Java基础第二遍-12-多线程-同步-通信
多线程安全问题原因
- 多个线程操作共享的数据
- 操作共享数据的线程有多个
- 其中一个线程操作共享数据的过程中,其他的线程参与了运算,就会产生线程安全问题
解决线程安全问题
- 锁机制
- 思路:将共享数据部分封装起来,当有线程进行操作的时候其他的线程不能参与运算,其中的线程执行完毕之后,其他的才可以参与运算
- 同步代码块可以解决这个问题:synchronized
同步的好处与弊端
- 好处:解决线程安全问题
- 弊端:将低了执行效率,同步外的线程都会判断同步锁
同步函数与同步代码块使用的锁
- 同步函数使用的锁是this,固定的
- 同步代码块可以使用任意的锁
- 所谓锁,就是一个对象
静态的同步函数使用的锁
- 使用的锁是该函数所属字节码文件对象,也就是Class对象,可以使用Object中的getClass方法获取,也可以用(当前类名.class)表示
创建线程的方式
- 继承Thread
- 实现Runnbale
- 将线程任务从线程的子类中分离出来,进行了单独的封装
- 避免了Java单继承的局限性
- 较为常用
- callable
- 线程池
多线程下的单例模式
- 饿汉式
- 类一加载就创建对象
- 懒汉式
2. 要使用的时候才创建对象 - 多线程下使用单例模式使用饿汉式,实现起来较为简单
- 懒汉式需要解决,线程不安全问题和效率问题
使用同步解决线程安全问题,使用if判断解决效率问题
//懒汉式
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s==null){
synchronized (Single.class){
if (s==null){
return new Single();
}
}
}
return s;
}
}
生产者消费者模式
synchronized模式实现:在多消费者多生产者模式的情况下,只能使用notifyAll()的方式,使用notify()的方式有可能死锁。
package com.bixiangdong.thread;
class Resource{
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set(String name){
while (flag){
try {
this.wait();
}catch (InterruptedException e){
}
}
this.name=name;
count++;
System.out.println(Thread.currentThread().getName()+"----生产者----"+this.name+count);
flag=true;
notifyAll();//这里使用notifyAll是因为如果 生产者有多个的话,notify唤醒的可能总是生产者的线程,然后因为flag=flase就总是等待,可能会发生死锁的情况
}
public synchronized void get(){
while (!flag){
try {
this.wait();
}catch (InterruptedException e){
}
}
System.out.println(Thread.currentThread().getName()+"----消费者----"+this.name+count);
flag=false;
notifyAll();
}
}
class Productor implements Runnable{
private Resource r;
public Productor(Resource r){
this.r=r;
}
@Override
public void run() {
while (true){
r.set("牛奶");
}
}
}
class Customer implements Runnable{
private Resource r;
public Customer(Resource r){
this.r=r;
}
@Override
public void run() {
while (true){
r.get();
}
}
}
public class Demo03 {
public static void main(String[] args) {
Resource resource = new Resource();
Productor productor = new Productor(resource);
Customer customer = new Customer(resource);
new Thread(productor).start();
new Thread(customer).start();
}
}
lock实现方式:JDK1.5支持了lock方式,可以实现一个lock有多个condition,可以实现生产者通知消费者,更加精准,生产者只用唤醒一个消费者就行,消费者也是如此
package com.bixiangdong.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource2{
private String name;
private int count=1;
private boolean flag=false;
//创建锁
Lock lock = new ReentrantLock();
//通过已有的锁创建两组监视器
Condition pro_con = lock.newCondition();
Condition cus_con = lock.newCondition();
public void set(String name){
//设置锁
lock.lock();
while (flag){
try {
pro_con.await();
}catch (InterruptedException e){
}
}
this.name=name;
count++;
System.out.println(Thread.currentThread().getName()+"----生产者----"+this.name+count);
flag=true;
//notifyAll();//这里使用notifyAll是因为如果 生产者有多个的话,notify唤醒的可能总是生产者的线程,然后因为flag=flase就总是等待,可能会发生死锁的情况
cus_con.signal();//只需要唤醒一个消费者就ok
//释放锁
lock.unlock();
}
public void get(){
lock.lock();
while (!flag){
try {
cus_con.await();
}catch (InterruptedException e){
}
}
System.out.println(Thread.currentThread().getName()+"----消费者----"+this.name+count);
flag=false;
//notifyAll();
pro_con.signal();
lock.unlock();
}
}
class Productor2 implements Runnable{
private Resource2 r;
public Productor2(Resource2 r){
this.r=r;
}
@Override
public void run() {
while (true){
r.set("牛奶");
}
}
}
class Customer2 implements Runnable{
private Resource2 r;
public Customer2(Resource2 r){
this.r=r;
}
@Override
public void run() {
while (true){
r.get();
}
}
}
public class Demo04 {
public static void main(String[] args) {
Resource2 resource = new Resource2();
Productor2 productor = new Productor2(resource);
Customer2 customer = new Customer2(resource);
new Thread(productor).start();
new Thread(productor).start();
new Thread(productor).start();
new Thread(customer).start();
new Thread(customer).start();
new Thread(customer).start();
}
}
wait和sleep的区别
- wait可以指定时间,也可以不指定
- sleep必须指定时间
在同步时,对cpu的执行权和锁的处理不同 - wait:释放执行权,释放锁
- sleep:释放执行权,不释放锁
线程的结束
- 线程中run方法结束,则线程结束
- 如何结束run方法,通过改变标志
标记法
package com.bixiangdong.thread;
class A1 implements Runnable{
public boolean flag = true;
@Override
public void run() {
while (flag){
System.out.println("一直运行"+Thread.currentThread().getName());
}
}
}
public class Demo05 {
public static void main(String[] args) {
A1 a1 = new A1();
new Thread(a1).start();
new Thread(a1).start();
int num=0;
for (;;){
num++;
if (num==100000){
a1.flag=false;
break;
// System.exit(0);
}
System.out.println("-----------");
}
}
}
如果线程处于冻结状态,不能读取标志状态,那么需要使用interrupt,其本质就是将一个冻结状态的线程,强行唤醒使cpu执行,然后判断标志状态,从而结束线程。
package com.bixiangdong.thread;
class A3 implements Runnable{
public boolean flag = true;
@Override
public void run() {
while (flag){
try {
wait();
}catch (InterruptedException e){
flag=false;//interrupt会抛出异常,需要进行处理
}
System.out.println("一直运行"+Thread.currentThread().getName());
}
}
}
public class Demo06 {
public static void main(String[] args) {
A3 a3 = new A3();
Thread t1 = new Thread(a3);
Thread t2 = new Thread(a3);
int num=0;
for (;;){
num++;
if (num==100000){
// a3.flag=false;
t1.interrupt();
t2.interrupt();
break;
// System.exit(0);
}
System.out.println("-----------");
}
}
}
守护线程setDaemon
- 类似于后台线程,同样具备执行资格、执行权
- 所有的前台线程结束了,那么后台线程无论出于什么状态都自动结束
- 格式:
Thread t1 = new Thread(new A1);
t1.setDaemon(true);
t1.start();
线程优先级
- setPriority 设置优先级(1-10),优先级高不一定百分百执行,只是执行的机率较高
- join 申请加入运行,争夺执行权,官方解释:等待此线程结束
- yield 释放执行权 - 线程礼让
package com.bixiangdong.thread;
class A4 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Thread.yield();//释放执行权 - 线程礼让
System.out.println("一直运行"+Thread.currentThread().getName());
}
}
}
public class Demo07 {
public static void main(String[] args) throws InterruptedException {
A4 a4 = new A4();
Thread t1 = new Thread(a4);
Thread t2 = new Thread(a4);
//join 申请加入运行,争夺执行权,官方解释:等待此线程结束
t1.join();
//setPriority 设置优先级(1-10),优先级高不一定百分百执行,只是执行的机率较高
t2.setPriority(Thread.MAX_PRIORITY);
//
int num=0;
for (;;){
num++;
if (num==100000){
// a3.flag=false;
t1.interrupt();
t2.interrupt();
break;
// System.exit(0);
}
System.out.println("-----------");
}
}
}