线程相关概念问题
-
线程是独立的执行路径
-
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程。gc线程
-
main()称之为主线程,为系统的入口,用于执行整个程序
-
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
-
对同一分资源操作时,会存在资源抢夺的问题,需要加入并发控制
-
线程会带来额外的开销,如cpu调度时间,并发控制开销
-
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
三大线程不安全案例
-
线程不安全一============抢票案例
-
问题一:抢同一张票
-
问题二:产生负数
-
// 不安全的线程一:买票 public class UnsafeThreadOfBuyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); Thread thread1 = new Thread(buyTicket,"test1"); Thread thread2 = new Thread(buyTicket,"test2"); Thread thread3 = new Thread(buyTicket,"test3"); Thread thread4 = new Thread(buyTicket,"test4"); Thread thread5 = new Thread(buyTicket,"test5"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } } class BuyTicket implements Runnable{ private Integer ticketNumbers = 10; boolean flag = true; @Override public void run() { while (flag){ buy(); } } public void buy(){ if (ticketNumbers <= 0){ flag = false; return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"->"+"买了第"+ticketNumbers--+"票!"); } }
-
线程不安全二========不安全的取钱
-
主要受同一个账户,如果不做并行处理,可能会出现两个人同事取钱,照成余额为负的请款
-
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION; // 线程不安全案例二:两人同时取钱 public class UnsafeGetMoney { public static void main(String[] args) { Account account = new Account(10000,"大黄棺材本"); Drawing me = new Drawing(account,10000,"wa"); Drawing DogHuang = new Drawing(account,10,"大黄"); me.start(); DogHuang.start(); } } // 账户 class Account{ // 构造方法 public Account(int money, String name) { this.money = money; this.name = name; } int money;//余额 String name;//用户名 } // 银行:模拟取款 class Drawing extends Thread{ Account account;//账户 // 取了多少钱 int drawingMoney; // 手里 现在还有多少钱 int nowMoney; public Drawing(Account account,int drawingMoney,String name){ super(name); this.account=account; this.drawingMoney=drawingMoney; } @Override public void run(){ // 判断有没有钱 if (account.money-drawingMoney<0) { System.out.println("账户余额不足!取不了!"); return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 卡内余额 account.money=account.money-drawingMoney; // 手里的钱 nowMoney +=drawingMoney; System.out.println(account.name+":您的账户余额为:"+account.money); System.out.println(this.getName()+"手里有"+this.nowMoney+"元钱!"); } }
-
线程不安全三========线程不安全问题
import java.util.ArrayList; import java.util.List; // 线程不安全的集合 public class UnsafeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); for (int i = 0 ; i < 10000 ; i++){ new Thread(()->list.add(Thread.currentThread().getName())).start(); } Thread.sleep(3000); System.out.println(list.size()); } }
=========安全操作
-
同步方法:pubLic synchronized void method(int args){}
-
synchronized方法和synchronized块
-
缺陷:若将一个大的方法申明为synchronized将会影响效率
-
方法里面需要修改的内容才需要锁,锁的太多,浪费资源
-
-
同步块:synchronized(obj){}
-
obj称为监视器
-
obj可以是任意对象,推荐使用共享资源作为同步监视器
-
同步方法中无需指定同步监视器,因为同步方法的监视器就是this这个对象本身,或者class
-
同步监视器的执行过程
1)第一个线程访问,锁定同步监视器,执行其中代码
2)第二个线程访问,发现同步监视器锁定,无法访问
3)第一个线程访问完毕,解锁同步监视器
4)第二个线程访问,发现同步监视器没有锁,访问然后锁定
-
-
-
买票同步:
public class ProtectThreadSafe { public static void main(String[] args) { SafeBuyTicket buyTicket = new SafeBuyTicket(); Thread thread1 = new Thread(buyTicket,"test1"); Thread thread2 = new Thread(buyTicket,"test2"); Thread thread3 = new Thread(buyTicket,"test3"); Thread thread4 = new Thread(buyTicket,"test4"); Thread thread5 = new Thread(buyTicket,"test5"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } } class SafeBuyTicket implements Runnable{ private Integer ticketNumbers = 10; boolean flag = true; @Override public void run() { while (flag){ buy(); } } public synchronized void buy(){ if (ticketNumbers <= 0){ flag = false; return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"->"+"买了第"+ticketNumbers--+"票!"); } }
-
取钱同步:
public class SafeGetMoney { public static void main(String[] args) { SafeAccount safeaccount = new SafeAccount(10000, "大黄棺材本"); SafeDrawing me = new SafeDrawing(safeaccount, 10000, "wa"); SafeDrawing DogHuang = new SafeDrawing(safeaccount, 200, "大黄想吃胖哥俩"); me.start(); DogHuang.start(); try { me.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } // 账户 class SafeAccount { // 构造方法 public SafeAccount(int money, String name) { this.money = money; this.name = name; } int money;//余额 String name;//用户名 } // 银行:模拟取款 class SafeDrawing extends Thread { SafeAccount safeaccount;//账户 // 取了多少钱 int drawingMoney; // 手里 现在还有多少钱 int nowMoney; public SafeDrawing(SafeAccount safeaccount, int drawingMoney, String name) { super(name); this.safeaccount = safeaccount; this.drawingMoney = drawingMoney; } @Override public synchronized void run() { // 锁的变化的量 synchronized (safeaccount) { System.out.println(safeaccount.name + ":您的账户余额为:" + safeaccount.money); System.out.println(this.getName() + "想取" + drawingMoney + "元钱!"); // 判断有没有钱 if (safeaccount.money - drawingMoney < 0) { System.out.println("账户余额不足!" + this.getName() + "取不了钱!"+"=========================交易结束"); return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 卡内余额 safeaccount.money = safeaccount.money - drawingMoney; // 手里的钱 nowMoney += drawingMoney; System.out.println(this.getName() + "取了" + this.nowMoney + "元钱!"); System.out.println(safeaccount.name + ":您的账户余额为:" + safeaccount.money+"=========================交易结束"); } } }
-
list线程安全:
import java.util.ArrayList; import java.util.List; // 线程安全解决 public class SafeListThread { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); for (int i = 0 ; i < 10000 ; i++){ new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(3000); System.out.println(list.size()); } }
-
安全的List集合-CopyOnWriteArrayList:
import java.util.concurrent.CopyOnWriteArrayList; // 安全的集合JUC public class SafeJucList { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0 ; i < 10000 ; i++){ new Thread(()->list.add(Thread.currentThread().getName())).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
死锁
-
多个线程互相调用对方需要的资源,形成僵持
// 死锁----多个线程互相调用对方需要的资源,形成僵持 public class DeadLock { public static void main(String[] args) { Makeup huang = new Makeup("大黄",1); Makeup zhou = new Makeup("周某",0); huang.start(); zhou.start(); } } // dog黄 class DogHuang{ } // pig周 class PigZhou{ } class Makeup extends Thread{ // 需要的资源只有一份,用static来保证 static DogHuang dogHuang = new DogHuang(); static PigZhou pigZhou = new PigZhou(); int choice; String name; public Makeup(String name, int choice) { this.name = name; this.choice = choice; } @Override public void run(){ try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } // 互现持有对方的锁,拿到对方的资源 private void makeup() throws InterruptedException { if(choice==0){ synchronized (dogHuang){ // System.out.println(this.name+"偷了大黄的家!"); Thread.sleep(1000); synchronized (pigZhou){ System.out.println(this.getName()+"回周某自己的家!"); } } }else { synchronized (pigZhou){ System.out.println(this.name+"偷了周某的家!"); Thread.sleep(1000); synchronized (dogHuang){ System.out.println(this.getName()+"回大黄自己的家!"); } } } } }
-
避免死锁
// 死锁----多个线程互相调用对方需要的资源,形成僵持 public class DeadLock { public static void main(String[] args) { Makeup huang = new Makeup("大黄",1); Makeup zhou = new Makeup("周某",0); huang.start(); zhou.start(); } } // dog黄 class DogHuang{ } // pig周 class PigZhou{ } class Makeup extends Thread{ // 需要的资源只有一份,用static来保证 static DogHuang dogHuang = new DogHuang(); static PigZhou pigZhou = new PigZhou(); int choice; String name; public Makeup(String name, int choice) { this.name = name; this.choice = choice; } @Override public void run(){ try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } // 互现持有对方的锁,拿到对方的资源 private void makeup() throws InterruptedException { if(choice==0){ synchronized (dogHuang){ // System.out.println(this.name+"偷了大黄的家!"); Thread.sleep(1000); }synchronized (pigZhou){ System.out.println(this.name+"回周某自己的家!"); } }else { synchronized (pigZhou){ System.out.println(this.name+"偷了周某的家!"); Thread.sleep(1000); }synchronized (dogHuang){ System.out.println(this.name+"回大黄自己的家!"); } } } }
-
死锁避免方法:
-
死锁产生需要的条件
1)互斥条件:一个资源每次只能被一个进程使用
2)请求与保持请求:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
========以上四个条件,只要想办法破解一个就可以避免死锁发生
-
Lock(锁)
-
与synchronized机制相同,但是lock锁是显示的,而synchronized时隐式的
-
ReentrantLock(可重入锁)----实现了Lock
import java.util.concurrent.locks.ReentrantLock; // 测试lock锁 public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); } } class TestLock2 implements Runnable{ private Integer ticketNumbers = 10; boolean flag = true; // 定义锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (flag){ try { lock.lock(); buy(); } finally { lock.unlock(); } } } public void buy(){ if (ticketNumbers <= 0){ flag = false; return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"->"+"买了第"+ticketNumbers--+"票!"); } }
-
需要注意的是,加锁和逻辑代码要放到try里面,为避免代码出错,必须将将解锁操作放到finally中
try { lock.lock(); buy(); } finally { lock.unlock(); }
synchronized与Lock的对比
1)Lock锁是显示锁(手动开启和关闭,切记莫要忘记关闭),而synchronized是隐式锁,出了作用域自动释放
2)Lock只有代码块锁,而synchronized有代码块锁和方法锁
3)使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多子类)
4)优先使用顺序:lock>同步代码块>同步方法
线程协作
-
生产者消费者模式------问题===================》解决线程通信
-
synchronized可阻止并发更新同一个共享资源,实现同步,但是不能实现不同线程之间消息的传递(通信)
Java提供的解决线程通信的方法
-
wait():表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
-
wait(long timeout):指定等待的毫秒数
-
notify():唤醒一个处于等待状态的线程
-
notifyAll():唤醒同一个线程上所有调用wait()方法的线程,优先级别高的线程优先调度
-
注意:均是Object类的方法,都只能在同步方法和同步代码块中使用,否则会抛出异常illegalMonitorStateException
-
解决方式
-
解决方式一:管程法
生产者:可能是方法、对象、线程、进程-----生产数据的模块
消费者:可能是方法、对象、线程、进程-----处理数据的模块
通过缓存区:生产者将数据放入缓存区,消费者从缓存区取出数据
// 管程法 public class BufferThread { public static void main(String[] args) { try { SynContainer container = new SynContainer(); new Productor(container).start(); new Customer(container).start(); }catch (Exception e){ e.printStackTrace(); } } } // 生产者 class Productor extends Thread{ SynContainer container; public Productor(SynContainer container) { this.container = container; } @Override public void run() { for (int i = 1 ; i <100 ;i++){ container.push(new Product(i)); System.out.println("生产了"+i+"只鸡"); } } } // 消费者 class Customer extends Thread{ SynContainer container; public Customer(SynContainer container) { if(container == null) { System.out.println("=====null========"); } this.container = container; } @Override public void run() { for (int i = 1 ; i <100 ;i++){ System.out.println("消费了了"+ container.pop().id+"只鸡"+"==="+i); } } } // 产品 class Product { int id; public Product(int id) { this.id = id; } } // 缓冲区 class SynContainer{ // 需要容器大小 Product[] products = new Product[10]; //容器计数器 int count= 0; public synchronized void push(Product product) { if (count == products.length){ // 通知消费者消费,生产等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 如果没装满,继续装入 products[count] = product; count++; // 通知消费者消费 this.notifyAll(); } public synchronized Product pop() { if (count == 0){ // 通知生产者生产,消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Product product = products[count]; // 消费完了,通知生产者生产 this.notifyAll();; return product; } }