一、创建线程
1、有一个实现了runnable接口的任务类
2、利用接口来创建任务
3、将任务放在线程中执行(线程由thread类创建,任务就是代码,需要加载进线程)
4、调用线程的start方法来运行线程
代码示例:并行打印100个a,与100个b。
import java.lang.Thread;
public class text
{
public static void main(String[] argc)
{
//创建任务
Runnable printA=new PrintChar('a',100);
Runnable printB=new PrintChar('b',100);
//创建线程并加载任务
Thread thread1=new Thread(printA);
Thread thread2=new Thread(printB);
//启动线程
thread1.start();
thread2.start();
}
static class PrintChar implements Runnable
{
private char data;
private int times;
PrintChar(char data,int times)
{
this.data=data;
this.times=times;
}
@Override
public void run()
{
for(int i=0;i<times;i++)
{
System.out.println(data);
}
}
}
}
二、线程池与数据不一致性
ExcutorService来管理线程池
Excutors中提供两个静态方法来创建线程池,
newFixedThreadPool(int);//固定池中线程数目,返回ExcutorService
newCachedThreadPool();//返回ExcutorService
import java.lang.Thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class text{
private static int balance;
public static void main(String[] args) {
ExecutorService executor= Executors.newCachedThreadPool();
for(int i=0;i<1000;i++)
{
executor.execute(new AddThead());
reduce();
}
executor.shutdown();//等所有线程结束就关闭线程池
while (!executor.isTerminated())
{
System.out.println(balance);//直接输出不行,主线程也是一个线程.主线程要循环检查是否全部关闭了
}
}
private static class AddThead implements Runnable
{
@Override
public void run()
{
add();//使用内部类协助完成增加的多线程
try
{
Thread.sleep(5);
}
catch (InterruptedException ex)
{
}
}
}
public static void add() {
balance++;
}
}
输出并不是1000,产生了数据不一致性
三、线程同步
一、synchronized关键字
在add()函数前加上synchronized就设立了临界区,可避免数据不一致
public synchronized static void add() {
balance++;
}
二、利用加锁同步
synchronized关键字是隐式加锁,Java还支持显示加锁。
一个锁是一个Lock接口的实例,ReentrantLock是Lock的具体实现,用于创建相互排斥的锁。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class text{
private static int balance;
public static void main(String[] args) {
ExecutorService executor= Executors.newCachedThreadPool();
for(int i=0;i<2000;i++)
{
executor.execute(new AddThead());
}
executor.shutdown();//等所有线程结束就关闭线程池
while (!executor.isTerminated())
{
System.out.println(balance);//直接输出不行,主线程也是一个线程.主线程要循环检查是否全部关闭了
}
}
private static class AddThead implements Runnable
{
//这里一定要用static,不然每个对象都有一把锁,都能自由访问,相当于没锁。
private static Lock lock=new ReentrantLock();
@Override
public void run()
{
lock.lock();
add();
lock.unlock();
}
}
public static void add(){
balance++;
}
}
一定要注意有几把锁,不要一个线程里一把,这样锁不住。
四、线程协作
条件是通过调用Lock对象的newCondition()方法创建的对象。有了条件对象,就可以利用
await():引起当前线程等待
signal():唤醒该锁上的一个等待线程
signalAll():唤醒该锁上所有的等待线程
方法来实现线程间的相互通信。
编程示例:一个线程每隔1秒就存10元,每次存完都唤醒取钱的。另一个线程一直想取12元,不够就睡眠,被唤醒就又尝试取钱。
代码链接.
编程示例:线程模拟生成者消费者问题
上一个银行账户存取款,在一个锁上只产生了一个条件,在生成者消费者问题中一个锁上有两个条件,分别代表不同的状态。它们相互唤醒。
生产者消费者代码链接.
总结
这两个编程示例的套路都是,资源自己实现具有锁的接口,多线程只管调用即可,某线程被什么条件睡眠,就得这个条件被解才能醒来。条件可以被自己唤醒,也可以被同一锁上的其它条件唤醒。
五、阻塞队列
阻塞队列是一种会阻塞线程的队列,又能存东西,又自带阻塞功能。线程A往阻塞队列里放东西,如果队列满了,则A被阻塞,如果A循环尝试放,当队列有位置时又可以放进去了。
取东西和放东西类似。
ArrayBlockingQueue:数组实现阻塞队列,构造函数必须指定队列容量。
LinkedBlockingQueue:链式阻塞队列,构造时可以不指定容量,这时可以在内存允许下放无数个元素。
PriorityBlockingQueue:优先队列,也可以不指定容量。注意:这里的优先是指元素位置的优先,不是阻塞线程谁优先。
阻塞队列简化生成者消费者:之前写的生成者消费者代码时废了好大的劲才写了一个具有阻塞线程功能的Buffer类,现在有阻塞队列了,就不用写buffer类了。
代码链接.
队列添加和取元素时用put和take,用offer和poll无阻塞功能
六、信号量
信号量操作:
Semaphore semaphore =new Semaphore(3);//允许三个线程
semaphore.acquire();//获得一个信号量
semaphore.release();//释放一个信号量
锁与信号量:
信号量多是用来同步的,协调顺序的。锁是用来保护资源,互斥的(互斥也是一种同步)。只有一个许可的信号量类似一个互斥锁。
七、死锁
发生线程之间相互等待资源,就叫发生了死锁。有没有死锁可以使用资源分配图来判断(每个资源只供一份)。
死锁示例.
八、同步合集
java合集中的类如线性表、集合、映射表等不是线程安全的。collections中提供了6个静态方法来将合集转化为同步版本。