多线程
程序是静止的,只有真正运行时的程序,才被称为进程。
单核CPU在任何时间点上,只能运行一个进程;宏观并行、微观串行。
附:电脑核数查询操作
-
什么是线程?
1.1 线程,又称为轻量级进程(Light Weight Process)程序中的一个顺序控制流程,同时也是CPU的基本调度单位。 1.2 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。 例如:迅雷是一个进程,当中的多个下载任务即是多线程。 Java虚拟机(不同的虚拟环境下模拟相同环境)是一个进程,当中默认包含主线程(Main),可通过代码创建多个独立线程,与Main并发执行。
-
线程的组成
2.1 任何一个线程都具有基本的组成部分: CUP时间片:操作系统(OS)会为每个线程分配执行时间。 2.2 运行数据: 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。 2.3 线程的逻辑代码 2.3.1 创建线程(1) 继承Thread类 覆盖run()方法 创建子类对象 调用start方法
public class TestExtendsThead {
public static void main(String[] args) {
MyThread t1 = new MyThread();//3.创建子类(线程)对象
MyThread2 t2 = new MyThread2();
//t1.run();//直接调用run方法,普通对象调用方法一样
t1.start();//4.调用start方法
t2.start();
//由JVM来调用run方法
for(int i = 1;i<50;i++) {
System.out.println("Main--"+i);
}
}
}
class MyThread extends Thread{//1.继承Thread类
@Override//2.覆盖run()方法
public void run() {//线程的任务
for(int i = 1;i<50;i++) {
System.out.println("MyThread----"+i);
}
}
}
class MyThread2 extends Thread{//1.继承Thread类
@Override//2.覆盖run()方法
public void run() {//线程的任务
for(int i = 1;i<50;i++) {
System.out.println("MyThread2-------"+i);
}
}
}
2.3.2 创建线程(2)
实现Runable接口
覆盖run()方法
创建线程对象
调用start方法
public class TestImplementsRunable {
public static void main(String[] args) {
//创建线程对象
MyRunnable mr1 = new MyRunnable();
//Thread线程类的有参构造方法
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr1);
t1.start();
t2.start();
}
}
//实现接口,只是将当前编程线程任务类,本身不是个线程
//任务是可以多个线程对象共享
//更灵活
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 1;i<50;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
- 线程的状态
2.3.3 常见方法:
休眠:
public static void sleep(long millis)
throws InterruptedException
当前线程主动休眠millis毫秒
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
MyRunnable task = new MyRunnable();
Thread t2 = new Thread(task);
t2.start();
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+i);
if(i==30) {//在特定条件下休眠,在指定时间内不去竞争时间片
//此时用的是类名调用sleep方法,sleep为静态方法
Thread.sleep(2000);//通知完t1后,main线程休眠2秒
}
}
}
}
class MyThread extends Thread{//1.继承Thread类
@Override//2.覆盖run()方法
public void run() {//线程的任务
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 1;i<=50;i++) {
if(i%2==0) {
System.out.println("线程2 休眠了");
try {//不能使用抛出,因为父类并没有抛出异常
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
放弃:
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
public class TestYield {
public static void main(String[] args) {
Thread t1 = new Thread(new Task());
t1.start();
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i%10 == 0) {
System.out.println("main主动放弃");
Thread.yield();//放弃!主动放弃当前持有时间片,进入下一次的竞争
}
}
}
}
class Task implements Runnable{
@Override
public void run() {
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
结合:
public final void join(long millis)
throws InterruptedException
允许其他线程加入到当前线程。
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Task2());
Thread t2 = new Thread(new Task2());
t1.start();
t2.start();
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i == 20) {
System.out.println("main执行到20了,执行t1");
t1.join();//将t1加入到main线程执行流程中,等待t1线程执行结束后,main再进行竞争时间片。
//无限期等待:等待条件为调用join方法的线程完毕后!再进入就绪状态,竞争时间
}
}
}
}
class Task2 implements Runnable{
@Override
public void run() {
for(int i = 1;i<=50;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
线程的状态(等待)
4. **线程安全**
线程不安全:
当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
原子操作:不可分割的多部操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
线程安全:
==同步方式(1)==
同步代码块:
```java
synchronized(临界资源对象){//对临界资源对象加锁
//代码(原子操作)
}
public class TestSynchronized {
public static void main(String[] args) {
//临界资源,被共享的对象
Account acc = new Account("6002","1234",2000);
//两个线程对象,共享同一银行卡对象.
Thread husband = new Thread(new Husband(acc),"丈夫");
Thread wife = new Thread(new Wife(acc),"妻子");
husband.start();
wife.start();
}
}
class Husband implements Runnable{
Account acc;
public Husband(Account acc) {
this.acc=acc;
}
@Override//线程任务:取款;
public void run() {
//synchronized(acc) {
this.acc.withdrawal("6002", "1234",1200);
//}
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc=acc;
}
@Override//线程任务:取款;
public void run() {
//synchronized(acc) {//如果丈夫先拿到了锁,进行原子操作,那么妻子会等。
this.acc.withdrawal("6002", "1234",1200);
//}
}
}
//银行账户 银行卡
class Account{
String cardNo;
String password;
double balance;
public Account(String cardNo, String password, double balance) {
super();
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
//取款(原子操作,从插卡验证到取款成功一系列步骤,不可缺少或打乱)
public void withdrawal(String no,String pwd,double money){
synchronized(this){
System.out.println(Thread.currentThread().getName()+"正在读卡。。。");
if(no.equals(this.cardNo)&&pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName()+"验证成功。。。");
if(money <= this.balance) {
try {
Thread.sleep(1000);//模拟现实世界,ATM机在数钱
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.balance = this.balance-money;
System.out.println(Thread.currentThread().getName()+"取款成功!当前余额为:"+this.balance);
}else {
System.out.println(Thread.currentThread().getName()+"当前余额不足!:");
}
}else {
System.out.println("卡号或密码错误!");
}
}
}
}
注:
- 每个对象都有一个互斥锁标记,用来分配给线程的。
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
- 线程退出同步代码块时,会释放相应的互斥锁标记。
同步方式(2)
同步方法:
synchronized 返回值类型 方法名称(形参列表0){//对当前对象(this)加锁
//代码(原子操作)
}
public class TestSynchronized{
public static void main(String[] args){
//临界资源,被共享的对象
Account acc = new Account("6002","1234",2000);
//两个线程对象,共享同一银行卡对象.
Thread husband = new Thread(new Husband(acc),"丈夫");
Thread wife = new Thread(new Wife(acc),"妻子");
husband.start();
wife.start();
}
}
class Husband implements Runnable {
Account acc;
public Husband(Account acc) {
this.acc=acc;
}
@Override//线程任务:取款;
public void run() {
//synchronized(acc) {
this.acc.withdrawal("6002", "1234",1200);
//}
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc=acc;
}
@Override//线程任务:取款;
public void run() {
//synchronized(acc) {//如果丈夫先拿到了锁,进行原子操作,那么妻子会等。
this.acc.withdrawal("6002", "1234",1200);
//}
}
}
//银行账户 银行卡
class Account{
String cardNo;
String password;
double balance;
public Account(String cardNo, String password, double balance) {
super();
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
//取款(原子操作,从插卡验证到取款成功一系列步骤,不可缺少或打乱)
public synchronized void withdrawal(String no,String pwd,double money) {
//synchronized(this){
System.out.println(Thread.currentThread().getName()+"正在读卡。。。");
if(no.equals(this.cardNo)&&pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName()+"验证成功。。。");
if(money <= this.balance) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//模拟现实世界,ATM机在数钱
this.balance = this.balance-money;
System.out.println(Thread.currentThread().getName()+"取款成功!当前余额为:"+this.balance);
}else {
System.out.println(Thread.currentThread().getName()+"当前余额不足!:");
}
}else {
System.out.println("卡号或密码错误!");
}
}
//}
}
注:
-
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
-
线程退出同步方法时,会释放相应的互斥锁标记。
同步规则
注意: -
线程池
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
已知JDK中线程安全的类:
StringBuffer
Vector
Hashtable
增删改操作加锁
查不加锁
以上类中的公开方法,均为synchronized修饰的同步方法。经典问题
死锁:
当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
public class TestDeadLock {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
MyThread1 t1 = new MyThread1(obj);
MyThread2 t2 = new MyThread2(obj);
t1.start();
t2.start();
Thread.sleep(3000);
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
//obj.wait();
//obj.notify();//随机唤醒一个拿执行代码
obj.notifyAll();//将obj中所有wait队列所有的线程都唤醒
System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
}
}
}
//简单:一个线程持有A对象的锁,另一个对象也想要:阻塞
//复杂:一个对象持有A对象的锁,需要B对象的锁,另一个持有B,想要A
class MyThread1 extends Thread{
Object obj;
public MyThread1(Object obj) {
this.obj = obj;
}
public void run() {
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
}
}
class MyThread2 extends Thread{
Object obj;
public MyThread2(Object obj) {
this.obj = obj;
}
public void run() {
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
}
}
public class TestDeadLock1 {
public static void main(String[] args) {
LeftChopstick left = new LeftChopstick();
RightChopstick right = new RightChopstick();
Thread b = new Thread(new Boy(left,right));
Thread g = new Thread(new Girl(left,right));
b.start();
g.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class LeftChopstick{
String name = "左筷子";
}
class RightChopstick{
String name = "右筷子";
}
class Boy implements Runnable{
LeftChopstick left;
RightChopstick right;
public Boy(LeftChopstick left, RightChopstick right) {
super();
this.left = left;
this.right = right;
}
@Override
public void run() {
System.out.println("男孩子要拿筷子");
synchronized(left) {
try {
left.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("男孩子拿到左筷子,开始拿右筷子");
synchronized(right) {
System.out.println("男孩子拿到右筷子,开始吃饭");
}
}
}
}
class Girl implements Runnable{
LeftChopstick left;
RightChopstick right;
public Girl(LeftChopstick left, RightChopstick right) {
super();
this.left = left;
this.right = right;
}
@Override
public void run() {
System.out.println("女孩子要拿筷子");
synchronized(right) {
System.out.println("女孩子拿到右筷子,开始拿左筷子");
synchronized(left) {
System.out.println("女孩子拿到左筷子,开始吃饭");
left.notify();
}
}
}
}
生产者、消费者:
若干个生产者在生产产品,这些产品将提供给若干消费者去消费,为了使生产者和消费者能并发执行,
在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,
显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取出产品,也不允许生产者向一个满的缓冲区中放入产品。
public class TestProductAndCustomer {
public static void main(String[] args) {
Shop shop = new Shop();
Thread p = new Thread(new Product(shop),"生产者");
Thread c = new Thread(new Customer(shop),"消费者");
p.start();
c.start();
}
}
class Goods{
private int id;
public Goods(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Shop{
Goods goods;
boolean flag;//标识商品是否充足
//生产者调用的 存的方法
public synchronized void saveGoods(Goods goods) throws InterruptedException {
//1.判断商品是否充足
if(flag == true) {//商品充足,生产者不用生产,而等待消费者买完!
System.out.println("生产者:商品充足!要等待了!");
this.wait();//商品充足,生产者不用生产,而等待消费者买完,进入等待状态
}
//商品不充足:生产者不生产商品,存到商城里
System.out.println(Thread.currentThread().getName()+"生产并在商城里存放了"+goods.getId()+"件商品");
this.goods = goods;
flag = true;//已经有商品了;可以让消费者购买了
//消费者等待
this.notifyAll();将等待的消费者唤醒。前来购买商品
}
//消费者调用的取得方法
public synchronized void buyGoods() throws InterruptedException {
if(flag == false) {//没有商品了,消费者就要等待
System.out.println("消费者:商品不充足!,要等待了");
this.wait();//消费者进入等待队列,等到生产者生产商品后,唤醒
}
//正常购买
System.out.println(Thread.currentThread().getName()+"购买了"+goods.getId()+"件商品");
//商品买完了;标识没货
this.goods =null;
flag = false;
//唤醒生产者去生产商品
this.notifyAll();
}
}
//生产者
class Product implements Runnable{
Shop shop;
public Product(Shop shop) {
this.shop = shop;
}
@Override
public void run() {
//通过循环,生产商品存到shop
for(int i =1; i<=30;i++) {
try {
//生产者线程调用存商品方法,传一个商品对象
this.shop.saveGoods(new Goods(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Customer implements Runnable{
Shop shop;
public Customer(Shop shop) {
this.shop = shop;
}
@Override
public void run() {
for(int i =1; i<=30;i++) {
try {
this.shop.buyGoods();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程通信
等待
public final void wait()
public final void wait(long timeout)
必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。
同时此线程阻塞在obj的等待队列中。释放锁,进入等待队列
通知
public final void notify()
public final void notifyAll()
必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响。
高级多线程
线程池概念
-
现有问题:
线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
-
线程池:
线程容器,可设定线程分配的数量上限。 将预先创建的线程对象存入池中,并重用线程池中的线程对象 避免频繁的创建和销毁
线程池原理
获取线程池
常用的线程池接口和类(所在包java.util.concurrent):
Executor:线程池的顶级接口
ExecutorService:线程池接口,可通过submit(Runnale task)提交任务代码
Executors工厂类:通过此类可以获得一个线程池。
通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
通过newCashedThreadPool()获取动态数量的线程池,如不够则创建新的,没有上限
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
//线程池(引用) ---->Executors工具类 (工厂类)
//ExecutorService es = Executors.newFixedThreadPool(3);//手动限定线程池中线程数量
ExecutorService es = Executors.newCachedThreadPool();//动态数量的线程池
//1.创建任务对象
MyTask task = new MyTask();
//Thread t = new Thread(task);
//es.submit(t);如此才是线程
//2.将任务提交到线程池,由线程池调度、执行
es.submit(task);//Runnable类型的对象
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
}
}
//线程任务
class MyTask implements Runnable{
@Override
public void run() {
for(int i = 1; i<=50;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
Callable接口
public interface Callable<V>{
public V call() throws Exception;
}
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
Callable具有泛型返回值,可以声明异常。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCallable {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
MyTask1 task = new MyTask1();
es.submit(task);
}
}
class MyTask1 implements Callable<Integer>{//1.有泛型
@Override
public Integer call() throws Exception {//2.可以声明异常
for(int i = 1;i<=50;i++) {
if(i == 30) {
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
}
Future接口
概念:异步接收ExecutorService.submit()所返回的状态结果,当中包含了接收call()的返回值
方法:V get()以阻塞形式等待Future中的异步处理(call()的返回值)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestFuture {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
MyCall mc = new MyCall();
Future<Integer> result =es.submit(mc);//通过submit执行提交的任务,Future接收返回的结果
Integer value = result.get();//通过Future的get方法,获得线程执行完毕后的结果
System.out.println(value);
MyCall2 mc2 = new MyCall2();
Future<Integer> result2 =es.submit(mc2);
Integer value2 = result2.get();
System.out.println(value2);
System.out.println(value2+value);
}
}
class MyCall implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyCall正在执行。。。");
return 100;
}
}
class MyCall2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyCall2正在执行。。。");
return 200;
}
}
使用两个线程,并发计算1—50、51—100的和,在进行汇总统计
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestFuture {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
MyCall mc = new MyCall();
Future<Integer> result =es.submit(mc);//通过submit执行提交的任务,Future接收返回的结果
Integer value = result.get();//通过Future的get方法,获得线程执行完毕后的结果
System.out.println(value);
MyCall2 mc2 = new MyCall2();
Future<Integer> result2 =es.submit(mc2);
Integer value2 = result2.get();
System.out.println(value2);
System.out.println(value2+value);
}
}
class MyCall implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyCall正在执行。。。");
Integer sum = 0;
for(int i = 1;i<=50;i++) {
sum=sum+i;
}
return sum;
}
}
class MyCall2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyCall2正在执行。。。");
Integer sum = 0;
for(int i = 51;i<=100;i++) {
sum=sum+i;
}
return sum;
}
}
线程的同步
同步:形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。单条执行路径
线程的异步
异步:形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。多条执行路径。
Lock接口
- JDK5加入,与synchronized比较,显示定义,结构更灵活
- ‘提供更多实用性方法,功能更强大、性能更优越。
- 常用方法:
void lock()//获取锁,如锁被占用,则等待。
boolean tryLock()//尝试获取锁(成功返回true,失败则false)
void unlock() //释放锁
重入锁
-
ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能。
使用Lock,需要明确的写上锁和释放锁 为了避免拿到锁的线程在运行期间出现异常,导致程序终止,没有释放锁! 应用try{}finally{}来保证,无论正确执行与否,最终都会释放锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Test obj = new Test();
Thread t1 = new Thread(new MyTask(obj));
Thread t2 = new Thread(new MyTask2(obj));
t1.start();
t2.start();
}
}
class Test{
Lock lock = new ReentrantLock();
public void method() {
//显示写上此处获得锁
try {
int i = 0;
lock.lock();
System.out.println(Thread.currentThread().getName()+"进入到上锁的方法里");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*//模拟程序出错
int a = 10/0; //遇到异常,导致锁没有释放,采用try finally
//使用try finally,抛两次异常,因为在遇到异常后执行了finally释放了锁。
*/
method();//无穷递归
}finally {
//显示写此处释放锁
lock.unlock();
}
}
}
class MyTask implements Runnable{
Test obj ;
public MyTask(Test obj) {
this.obj = obj;
}
@Override
public void run() {
//synchronized(obj) {//Lock锁不能在此处写。很明确对谁上锁。充当锁对象
//}
obj.method();
}
}
class MyTask2 implements Runnable{
Test obj ;
public MyTask2(Test obj) {
this.obj = obj;
}
@Override
public void run() {
obj.method();
}
}
读写锁
-
ReentrantReadWriteLock:
一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。 支持多次分配读锁,使多个读操作可以并发执行。
-
互斥规则
写-写:互斥,阻塞。 读-写:互斥,读阻塞写、写阻塞读。 读-读:不互斥、不阻塞。 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
//写和读都用lock锁,性能太低
//换读写锁,ReentrantReadWriteLock 写同步 读异步
public class TestReadWriteLock {
public static void main(String[] args) {
Student stu = new Student();//共享资源对象
ExecutorService es = Executors.newFixedThreadPool(20);
WriteTask write = new WriteTask(stu);//写线程任务
ReadTask read = new ReadTask(stu);//读线程任务
//执行两个赋值的线程任务
long start = System.currentTimeMillis();//开始时间,毫秒值
//但是此时主线程没停止,时间不准确
es.submit(write);
es.submit(write);
for(int i = 1;i<=18;i++) {
es.submit(read);
}//当读也加锁时,读一个1秒,共18秒,性能下降。读不应该同步,读应该异步,因为不会对数据修改
//ExecutorService 中的方法
//停止线程池,但是不停止已经提交的任务,等已提交任务都执行完
es.shutdown();
//询问线程池,任务结束了嘛?
while(true) {
System.out.println("结束了嘛?");
if(es.isTerminated()==true) {//证明线程任务都执行完毕
break;
}
}
long end = System.currentTimeMillis();//结束时间
System.out.println(end-start);
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//System.out.println(stu.getAge());
}
}
class Student{
private int age;
//Lock lock = new ReentrantLock();
ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
ReadLock read = rrw.readLock();//读锁
WriteLock write = rrw.writeLock();//写锁
//读年龄
public int getAge(int age) {//也加锁
read.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return age;
}finally {
read.unlock();
}
}
//写年龄
public void setAge(int age) {
write.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.age = age;
}finally {
write.unlock();
}
}
}
class WriteTask implements Callable{
Student stu;
public WriteTask(Student stu) {
this.stu = stu;
}
public Object call() throws Exception {
stu.setAge(100);
return null;
}
}
class ReadTask implements Callable{
Student stu;
public ReadTask(Student stu) {
this.stu = stu;
}
public Object call() throws Exception {
stu.getAge(100);
return null;
}
}
线程安全的集合
Collection体系集合下,除Vector以外的线程安全集合。
Collections中的工具方法
-
Collections工具类提供了多个可以获得线程安全集合的方法
public static <T> Collection<T> synchronizedCollection(Collection<T> c) public static <T> List<T> synchronizedList(List<T> list) public static <T> Set<T> synchronizedSet(Set<T> s) public static<K,V>Map<K,V>synchronizedMap(Map<K,V>m) public static <T>SortedSet<T>synchronizedSortedSet(SortedSet<T>s) public static<K,V>SortedMap<K,V>synchronizedSortedMap(SortedMap<K,V>m)
-
JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchronized实现。
Vecto中的add方法使用了synchronized方法
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
List<String> list = new ArrayList<String>();//0X1122233
List<String> safeList = Collections.synchronizedList(list);//0x34567
safeList.add("A");//SynchronizedList里的add方法。该方法里加了个锁
静态方法类名直接调用此方法传List类型的集合,其中SynchronizedList为Collections的静态内部类,不依赖外部类对象可直接通过类名访问,必要的功能组件。List下的实现类。将不安全的的集合传到有参构造方法里。最后将list传给SynchronizedList中定义的List<E> list,所以return回来的集合是SynchronizedList中定义的List <E> list
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
.....
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
CopyOnWriteArrayList
- 线程安全的ArrayList,加强版读写分离
- 写有锁,读无锁,读写之间不阻塞,优于读写锁
- 写入时,先copy一个容器副本、再添加新元素,最后替换引用
- 使用方式与ArrayList无异
CopyOnWriteArrayList alist = new CopyOnWriteArrayList();
alist.add(“A”);
jdk1.8底层代码:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//拿到底层数组扩容
newElements[len] = e;//newElements[0]=e
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
...
public E get(int index) {
return get(getArray(), index);
}
应用:
import java.util.concurrent.CopyOnWriteArrayList;
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
CopyOnWriteArrayList<String> alist = new CopyOnWriteArrayList<String>();
//写操作
alist.add("A");//将底层数组做一次复制,写的是新数组,写完操作后将新数组替换旧数组
alist.add("B");//每调用一次,底层方法扩容一次
//读操作
alist.get(1);//读的是写操作完成之前的旧数组:写完之后,才能读到新数组的新值。
}
}
CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现
- 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
- 如存在元素,则不添加(扔掉副本)
JDK1.8底层:
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
...
public boolean add(E e) {
return al.addIfAbsent(e);
}
//实现不重复
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
应用:
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
CopyOnWriteArrayList<String> alist = new CopyOnWriteArrayList<String>();
//写操作
alist.add("A");//将底层数组做一次复制,写的是新数组,写完操作后将新数组替换旧数组
alist.add("B");//每调用一次,底层方法扩容一次
//读操作
alist.get(1);//读的是写操作完成之前的旧数组:写完之后,才能读到新数组的新值。
//无序、无下标、不允许重复
CopyOnWriteArraySet<String> aset = new CopyOnWriteArraySet<String>();
//写操作,表面使用的是add方法,底层实际是用的CopyOnWriteArrayList的addIfAbsent()来判断要插入的新值是否存在
aset.add("A");
aset.add("B");
aset.add("C");
aset.add("E");
aset.add("F");
aset.add("G");
for(String s:aset) {
System.out.println(s);
}
}
}
ConcurrentHashMap
- 初始容量默认为16段(Segment),使用分段锁设计。
- 不对整个Map加锁,而是为每个Segment加锁。
- 当多个对象存入同一个Segment时,才需要互斥。
- 最理想状态为16个对象分别存入16个Segment,并行数量16。
- 使用方式与HashMap无异。
//分段锁设计 Segment 1.7的语法
//CAS交换算法和同步锁 JDK1.8 V:要更新的变量 E:预期值 N:新值
ConcurrentHashMap<String,String> map = new ConcurrentHashMap<String,String>();
map.put("A","skfjhd");
Queue接口(队列)
- Collection的子接口,表示队列FIFO(First In First Out)
- 常用方法:
抛出异常:
boolean add(E e)//顺序添加一个元素(到达上限后,在添加则会抛出异常)
E remove()//获得第一个元素并移除(如果队列没有元素时,则抛出异常)
E element()//获取第一个元素但不移除(如果队列没有元素时,则抛出异常)
返回特殊值:推荐使用
boolean offer(E e)//顺序添加一个元素(到达上限后,再添加则会返回false)
E poll()//获得第一个元素并移除(如果队列没有元素时,则返回null)
E keep()//获得第一个元素但不移除(如果队列没有元素时,则返回null)
import java.util.LinkedList;
import java.util.Queue;
public class TestQueues {
public static void main(String[] args) {
//Queue<String> q = new
//列表,尾部添加 (指定下标)
//链表:头尾添加
//队列:FIFO
//可以使用接口指向实现类对象
LinkedList<String> link = new LinkedList<String>();//链表
link.offer("A");
link.offer("B");
link.offer("C");
//用列表的方式打乱了FIFO队列的规则
link.add(0,"D");
System.out.println(link.peek());//
}
}
ConcurrentLinkedQueue
- 线程安全、可高效读写的队列,高并发下性能最好的队列。
- 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
- V: 要更新的变量 E: 预期值 N:新值
- 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
//严格遵守了队列的规则,其线程是安全的,采用了CAS算法
Queue<String> q = new ConcurrentLinkedQueue<String>();
//1.抛出异常 2.返回结果的
q.offer("A");
q.offer("B");
q.offer("C");
q.poll();//删除表头
System.out.println(q.peek());
BlockingQueue接口(阻塞队列)
-
Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
-
方法:
void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待。
E take()//获取并移除此队列头部元素,如果没有可用元素,则等待。 -
可用于解决生产者、消费者问题
阻塞队列
-
ArrayBlockingQueue:
数组结构实现,有界队列。(手工固定上限) -
LinkedBlockingQueue:
链表结构实现,无界队列。(默认上限Integer.MAX_VALUE)
BlockingQueue<String> bq = new ArrayBlockingQueue<String>(3);//手动固定队列上限
BlockingQueue<String> bql = new LinkedBlockingQueue<String>();//无界队列。最大有Integer.MAX_VALUE