Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
优先级高的不一定先执行,大多数情况是这样的。
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调度了。
优先级的设定建议在
start()
调度前,setPriority
之后紧接start()
thread.setDaemon(true);//默认false表示是用户线程
该thread
即使是永远运行,也会结束,因为是守护线程,JVM不会等待,用户线程结束之后即结束。
多个线程操作同一个资源。每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized
,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
不安全案例:(ArrayList
是线程不安全的)
public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } System.out.println(list.size()); }
可能的原因:1.可能有些线程还在执行时,就输出了size()
2.某个瞬间,往list
同一位置添加了两次覆盖了
同步方法: synchronized
默认锁的是this
这个对象本身(后续反射章节会有涉及)
public synchronized void method(int args){}
同步块:synchronized(obj){}
举例一个不安全银行取钱的例子:
public class TestAccount { public static void main(String[] args) throws InterruptedException { Account account = new Account("sum",100); Drawing you = new Drawing(account,50,"you"); Drawing girlfriend = new Drawing(account,70,"girl"); new Thread(you).start(); new Thread(girlfriend).start(); } } class Account{ String name; int Money; public Account(String name,int money){ this.Money=money; this.name=name; } } class Drawing implements Runnable{ Account account; int drawingMoney; int nowMoney=0; String name; public Drawing(Account account,int drawingMoney, String name) { this.account = account; this.drawingMoney = drawingMoney; this.name = name; } @Override public void run() { if(account.Money-drawingMoney < 0){ System.out.println("there's no money"); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.Money = account.Money - drawingMoney; nowMoney = nowMoney+drawingMoney; System.out.println("bank remains "+account.Money); System.out.println(name+" get "+nowMoney); } }
结果如下:
银行没有判断出钱不够,这里的sleep
放大了事故可能性。
增加同步块:
@Override public void run() { synchronized (account){ if(account.Money-drawingMoney < 0){ System.out.println("there's no money"); return ; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.Money = account.Money - drawingMoney; nowMoney = nowMoney+drawingMoney; System.out.println(name+" get "+nowMoney); System.out.println("bank remains "+account.Money); } }
共同操作的是account
所以obj即为它,同步块里即为原来的方法块,可以锁任何对象(需要锁操作的共同资源对象),这是和synchronized
方法不同之处。
用该方法改进上方的不安全ArrayList
public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(1000); System.out.println(list.size()); }
输出即为1000。
提一下并发编程中的安全List(CopyOnWriteArrayList
)
public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(5000); System.out.println(list.size()); }
这里的List是线程安全的。
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源释放才能运行。某个同步块同时拥有“两个以上对象的锁”时,就可能发生死锁。
补充:synchronized(obj){}
其实可以理解为等待obj
释放锁之后执行代码块
产生死锁的四个必要条件:
JDK5.0开始,提供了更强大的线程同步机制,通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具。ReentrantLock
是Lock
的常用实现类(可重入锁)。
class TestLock2 implements Runnable{ int ticketNums = 10; private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try { lock.lock();//加锁 if(ticketNums>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); }else{ break; } } finally { //解锁 lock.unlock(); } } } }
仍然是抢票举例,.lock()
加锁,.unlock()
解锁,以上结果没有冲突情况。
在这个问题中,仅用synchronized
是不够的
synchronized
可阻止并发更新同一个共享资源,实现了同步synchronized
不能用来实现不同线程之间的通信解决方式1:管程法
public class TestPC { //利用缓冲区解决 public static void main(String[] args) { Buffer buffer = new Buffer(); new Producer(buffer).start(); new Consumer(buffer).start(); } } class Producer extends Thread{ Buffer buffer; public Producer(Buffer buffer){ this.buffer = buffer; } @Override public void run() { for (int i = 1; i < 100; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } buffer.push(new product(i)); System.out.println("生产了第"+i+"个产品"); } } } class Consumer extends Thread{ Buffer buffer; public Consumer(Buffer buffer){ this.buffer = buffer; } @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消费了第"+buffer.pop().id+"个产品"); } } } class product{ int id; public product(int id) { this.id = id; } } class Buffer{ product[] pro = new product[10]; //缓存区大小 int count=0; public synchronized void push(product p){ if(count == pro.length){ //通知等待消费 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } pro[count] = p; count++; //通知可以消费了 this.notifyAll(); } public synchronized product pop() { if (count == 0) { //等待生产 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count--; product p2 = pro[count]; this.notifyAll(); return p2; } }
此段代码只涉及了新的wait()
和notifyAll()
,其余过程和操作系统相关知识描述一致,可以理一下思路。
解决方式2:信号灯法(利用标志位)建立一个(true,false)标志位进行判断,操作系统相关知识已有,不再详细描述
背景:经常创建和销毁,使用量特别大
思路:提前创建好多个线程,放在线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用。
好处:
corePoolSize
:核心池大小
maximumPoolSize
:最大线程数
keepAliveTime
:线程没有任务时最多保持多长时间后会终止
简单举例:
public class TestPool { public static void main(String[] args) { //创建服务,创建线程池 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭链接 service.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
线程池相关知识将在并发编程部分详细介绍。