ArrayList 是线程不安全的
- ArrayList是线程不安全的,而juc下CopyOnWriteArrayList是线程安全的。
//测试JUC安全类型的集合
public class Test1 {
public static void main(String[] args) throws InterruptedException {
//多个线程同时往CopyOnWriteArrayList加入元素
CopyOnWriteArrayList<String> list1=new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{list1.add(Thread.currentThread().getName());}).start();
}
//多个线程同时往ArrayList加入元素
ArrayList<String> list2 =new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{list2.add(Thread.currentThread().getName());}).start();
}
//主线程等待所有线程跑完再输出
Thread.sleep(10000);
System.out.println("list1 has "+list1.size()+" elements");
System.out.println("list2 has "+list2.size()+" elements");
}
}
Lock锁
- 从JDK5.0开始,java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock充当
- java.util.concurrent.locks.Lock接口是龙之多个线程对共享资源访问的工具。锁提供了副共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象
- ReentrantLock类实现了Lock,它拥有和synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以加显示锁、施放锁。
//测试Lock
public class Test2 {
public static void main(String[] args) {
Lock lock=new Lock();
new Thread(lock).start();
new Thread(lock).start();
new Thread(lock).start();
}
}
class Lock implements Runnable{
int num=10;
//定义Lock
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
//加锁
lock.lock();
if(num>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+num--);
}else{
break;
}
}finally {
//解锁
lock.unlock();
}
}
}
}
- Lock 和 synchronized关键字的一些区别:
- Lock是显示锁,手动开启和关闭,而synchronized关键字是隐式锁,出了作用域自动施放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM花费较少的时间来调度线程,性能更好。而且具有更好的扩展性
- 最好是优先使用Lock锁,少用synchronized的方法锁
- 注意:一般Lock锁写在try-finally中,在finally中施放锁。
** 线程协作 **
生产者和消费者问题
-
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,要马上通知消费者消费
-
对于消费者,在消费之后,要通知生产者已经结束消费,需要生成新的产品以供消费
-
在此问题中,只有synchronized是不够的
- 可以阻止多个线程同时更新同一个资源,实现了同步。
- 但是不能实现线程之间传递信息
-
java提供了几个方法来解决线程间通信问题
方法名 | 作用 |
---|---|
wait() | 线程一直等待,直到其他线程通知,与sleep不同,wait会施放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait方法的线程,优先级高的优先调度 |
- 注意:都是Object类中的方法,都只能在同步方法或者同步代码块中使用,否则抛出IllegalMonitorStateException异常
解决方式1---->管程法
- 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
- 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
- 缓冲区:消费者不能直接使用生产者的数据
- 生产者把生产好的数据存进缓冲区,消费者从缓冲区中获得数据
//测试管程法
//生产者 消费者 产品 缓冲区
public class Test1 {
public static void main(String[] args) {
SynContainer container=new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container){
this.container=container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i+1));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken chicken=container.pop();
}
}
}
//产品
class Chicken{
int index;
public Chicken(int index){
this.index=index;
}
}
class SynContainer{
//容器大小
Chicken[] chickens =new Chicken[10];
int count=0;
//放入产品方法
public synchronized void push(Chicken chicken){
if (count==chickens.length){
//通知消费者消费,生产等待
try {
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
//丢入产品
System.out.println("正在生产第"+chicken.index+"只鸡");
chickens[count]=chicken;
count++;
//通知消费者消费
notifyAll();
}
//消费方法
public synchronized Chicken pop(){
//判断能否消费
if (count==0){
//等待生产者生产,消费者等待
try {
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
count--;
Chicken chicken=chickens[count];
System.out.println("正在消费第"+chicken.index+"只鸡");
chickens[count]=null;
notifyAll();
return chicken;
}
}
解决方式2---->红绿灯法
- 使用添加标志位的方法来进行通信
//信号灯法:通过标志位解决
public class Test2 {
public static void main(String[] args) {
TV tv=new TV();
new Watcher(tv).start();
new Player(tv).start();
}
}
//生产者
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.play("节目"+i);
}
}
}
//消费者
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
//产品
class TV{
String voice;
boolean flag=true;
//生产
synchronized public void play(String voice){
if (!flag){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("正在表演:"+voice);
this.voice=voice;
this.flag=!this.flag;
notifyAll();
}
//消费
synchronized void watch(){
if (flag){
try {
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("正在观看:"+voice);
//通知表演
voice=null;
this.flag=!this.flag;
notifyAll();
}
}
}
线程池
- 经常创建和销毁、使用量特别大的资源,如并发状况下的线程,对性能影响很大
- 提前创建好多个线程,放入线程池中,使用时直接获取,使用完毕再放回线程池中。可以避免频繁创建销魂、实现重复利用。
- 好处:
- 提高响应速度(减少创建线程损耗)
- 降低资源消耗(线程重复利用,不需要每次都创建新线程)
- 便于管理
- corePoolSize:线程池大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时保持最多多长时间会终止
public class Test3 {
public static void main(String[] args) {
//创建线程池
//参数为线程池大小
ExecutorService service= Executors.newFixedThreadPool(4);
//执行
service.execute(new MyThread());
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() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}