文章目录
多线程
- 进程是资源分配的单位,线程是调度和执行的单位。
- 多线程的三种方式,继承Thread类,实现Runnable接口,实现Callable接口,前两种常用,第二个更常用,因为无法多继承,可以实现多个接口
- 继承Thread类,重写run方法,构建对调用start方法
- 实现Runnable接口,重写run方法,通过new Thread(对象).start()调用
Thread
- Thread示例,start方法并不一定立即调用,由CPU调用,下面的例子每次调用输出的结果都不一致
/**
* Thread多线程
* 继承Thread,重写run方法,构建对象并调用start方法
*/
public class Test extends Thread{
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println(i);
}
}
public static void main(String[] args) {
//创建对象
Test t = new Test();
//调用start方法
t.start();
//如果不用start而是调用run方法,那么还是单线程,先调用run再继续下面的
//上面开启另一个线程后继续下面的线程
for(int i = 0;i<10;i++){
System.out.println("------"+i);
}
}
}
- Thread示例2,多线程下载图片
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class PictureDownload extends Thread{
private String url;
private String name;
public PictureDownload(String url,String name){
this.url=url;
this.name=name;
}
public void run(){
DownloadURL d = new DownloadURL();
d.download(url,name);
}
//main
public static void main(String[] args) {
//启动三个线程
String url1="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332907&di=d4a320f4ca1c79d9352f41ff20b44f85&imgtype=0&src=http%3A%2F%2Fsem.g3img.com%2Fg3img%2Fntdljy888%2Fc2_20171019113746_28237.jpg";
String target1 = "testdata/A/1.jpg";
String url2="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332908&di=a5be915f708913cd6e0063e48b4acb8c&imgtype=0&src=http%3A%2F%2Fwww.znxkedu.com%2Fquxuecimg2016%2Fimage%2F20191023%2Fpxems_6201007.jpg";
String target2="testdata/A/2.jpg";
String url3="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332908&di=d7c6168f33b24c213279c0b0428e0317&imgtype=0&src=http%3A%2F%2Fqd.java.tedu.cn%2Fupload%2F20171013%2F20171013165916_450.jpg";
String target3="testdata/A/3.jpg";
PictureDownload pd1 = new PictureDownload(url1,target1);
PictureDownload pd2 = new PictureDownload(url2,target2);
PictureDownload pd3 = new PictureDownload(url3,target3);
pd1.start();
pd2.start();
pd3.start();
}
}
class DownloadURL{
/**
* 根据url下载到target文件
* @param url
* @param target
*/
public void download(String url,String target){
try {
FileUtils.copyURLToFile(new URL(url),new File(target));
System.out.println(target+"..下载成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Runnable
- 调用时使用new Thread(Runnable对象).start()方法,编写代码由extends Thread改为implements Runnable,其他相同
- 应优先使用接口
-
- Runnable不能抛出异常,返回值为void
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class PicdownloadRunnable implements Runnable{
private String url;
private String name;
public PicdownloadRunnable(String url,String name){
this.url=url;
this.name=name;
}
public void run(){
DownloadURL d = new DownloadURL();
d.download(url,name);
}
//main
public static void main(String[] args) {
//启动三个线程
String url1="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332907&di=d4a320f4ca1c79d9352f41ff20b44f85&imgtype=0&src=http%3A%2F%2Fsem.g3img.com%2Fg3img%2Fntdljy888%2Fc2_20171019113746_28237.jpg";
String target1 = "testdata/A/11.jpg";
String url2="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332908&di=a5be915f708913cd6e0063e48b4acb8c&imgtype=0&src=http%3A%2F%2Fwww.znxkedu.com%2Fquxuecimg2016%2Fimage%2F20191023%2Fpxems_6201007.jpg";
String target2="testdata/A/21.jpg";
String url3="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332908&di=d7c6168f33b24c213279c0b0428e0317&imgtype=0&src=http%3A%2F%2Fqd.java.tedu.cn%2Fupload%2F20171013%2F20171013165916_450.jpg";
String target3="testdata/A/31.jpg";
PictureDownload pd1 = new PictureDownload(url1,target1);
PictureDownload pd2 = new PictureDownload(url2,target2);
PictureDownload pd3 = new PictureDownload(url3,target3);
new Thread(pd1).start();
new Thread(pd2).start();
new Thread(pd3).start();
}
}
- Runnable共享资源,类似多线程抢票,需要考虑数据的准确性
public class TestTicket implements Runnable {
private int tickets = 99;
@Override
public void run() {
while (true){
if(tickets<0){
break;
}
try {
Thread.sleep(100); //有网络延迟时,会出现负数
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+tickets--);
}
}
public static void main(String[] args) {
TestTicket tk = new TestTicket();
//一份资源多个线程争夺
new Thread(tk ,"1号线").start();
new Thread(tk,"2号线").start();
new Thread(tk,"3号线").start();
}
}
Callable
- 可以抛异常,有返回值
- 并发用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class PicdownloadCallable implements Callable<Boolean> {
private String url;
private String name;
public PicdownloadCallable(String url, String name){
this.url=url;
this.name=name;
}
//重写call方法而不是run,需要返回值
public Boolean call(){
DownloadURL d = new DownloadURL();
d.download(url,name);
return true;
}
//main
public static void main(String[] args) {
//启动三个线程
String url1="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332907&di=d4a320f4ca1c79d9352f41ff20b44f85&imgtype=0&src=http%3A%2F%2Fsem.g3img.com%2Fg3img%2Fntdljy888%2Fc2_20171019113746_28237.jpg";
String target1 = "testdata/B/11.jpg";
String url2="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332908&di=a5be915f708913cd6e0063e48b4acb8c&imgtype=0&src=http%3A%2F%2Fwww.znxkedu.com%2Fquxuecimg2016%2Fimage%2F20191023%2Fpxems_6201007.jpg";
String target2="testdata/B/21.jpg";
String url3="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582086332908&di=d7c6168f33b24c213279c0b0428e0317&imgtype=0&src=http%3A%2F%2Fqd.java.tedu.cn%2Fupload%2F20171013%2F20171013165916_450.jpg";
String target3="testdata/B/31.jpg";
PicdownloadCallable pd1 = new PicdownloadCallable(url1,target1);
PicdownloadCallable pd2 = new PicdownloadCallable(url2,target2);
PicdownloadCallable pd3 = new PicdownloadCallable(url3,target3);
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> result1 = service.submit(pd1);
Future<Boolean> result2 = service.submit(pd2);
Future<Boolean> result3 = service.submit(pd3);
System.out.println(result1);
//关闭服务
service.shutdown();
}
}
静态代理
- 类似new Thread(Runnable对象).start()方法
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
//new Thread(线程对象).start();
}
}
interface Marry{
void happyMarry();
}
//真实角色
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("you and 嫦娥终于奔月了....");
}
}
//代理角色
class WeddingCompany implements Marry{
//真实角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
ready();
this.target.happyMarry();
after();
}
private void ready() {
System.out.println("布置猪窝。。。。");
}
private void after() {
System.out.println("闹玉兔。。。。");
}
}
lambda表达式
- java1.8开始有的功能
- 实现函数式接口的时候可以用lambda表达式。函数式接口是指有且仅有一个抽象方法的接口(可以有其他的方法,默认,静态,私有)
- 表达式为
接口的对象 = (参数1,参数2...) -> {函数体};
,这个函数就是接口定义的抽象方法 - @FunctionalInterface注解检查接口是否为函数式接口,如果不是则会报错
- 一些示例如下
public class TestLambda {
public static void main(String[] args) {
interfaceA a;
a = () -> System.out.println("汪汪汪");
a.shout();
//只有一个参数的时候可以省略括号
interfaceB b;
b = (num -> {
for(int i = 0;i<num;i++){
System.out.print("汪");
}
System.out.println();
});
b.shout(10);
//两个参数的时候
interfaceC c ;
c = (int x,int y) ->{
System.out.println("汪汪"+(x+y));
};
c.shout(3,4);
//有返回值
interfaceD d;
d = (int x,int y) ->{
System.out.println("hh");
return x+y;
};
System.out.println(d.shout(20,39));
interfaceD.run();
}
}
interface interfaceA{
void shout();
}
interface interfaceB{
void shout(int num);
}
interface interfaceC{
void shout(int x,int y);
}
@FunctionalInterface
interface interfaceD{
int shout(int x,int y);
static void run(){
System.out.println("快跑");
}
}
- 在多线程中使用lambda表达式,两个线程只需要用lambda的方式实现Runnable接口的void run()方法
public class TestLambdaThread {
public static void main(String[] args) {
/**
* lambda表达式的多线程
* 线程1和线程2会同时运行,每次得到的结果不一样
* lambda实现了Runnable接口的void run()方法
*/
//线程1
new Thread(()->{
for(int i = 0;i<100;i++){
System.out.println(i);
}
}).start();
//线程2
new Thread(()->{
for(int i = 0;i<100;i++){
System.out.println("------"+i);
}
}).start();
}
}
线程状态
- 新生new,就绪start,运行,阻塞,死亡五个状态
-
新生状态(New): 用new关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。
-
就绪状态(Runnable): 处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。有4中原因会导致线程进入就绪状态:
1. 新建线程:调用start()方法,进入就绪状态; 2. 阻塞线程:阻塞解除,进入就绪状态; 3. 运行线程:调用yield()方法,直接进入就绪状态; 4. 运行线程:JVM将CPU资源从本线程切换到其他线程。
-
运行状态(Running):在运行状态的线程执行自己run方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。
-
阻塞状态(Blocked):阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。有4种原因会导致阻塞:
1. 执行sleep(int millsecond)方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。 2. 执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,它进入就绪状态。 3. 线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。 4. join()线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法。
-
死亡状态(Terminated):死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作; 另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。 当一个线程进入死亡状态以后,就不能再回到其它状态了
终止
- 正常执行完毕结束
- 外部标记,调用方法结束
public class TestStopThread implements Runnable{
private boolean flag = true;// 标记变量,表示线程是否可中止
private String name ;
public TestStopThread(String name) {
this.name = name;
}
public void run(){
int i =0;
//当flag的值是true时,继续线程体;false则结束循环,继而终止线程体
while (flag){
System.out.println(name+"--->"+i++);
}
}
public void stop(){
flag = false;
}
public static void main(String[] args) {
TestStopThread thread = new TestStopThread("线程一");
new Thread(thread).start();
for(int i = 0;i<100;i++){
System.out.println("main------------"+i);
if (i==70){
//i为70的时候终止线程1
thread.stop();
System.out.println("线程一已终止");
}
}
}
}
阻塞 sleep yield
- 暂停线程执行常用的方法有sleep()和yield()方法,这两个方法的区别是:
1. sleep()方法:可以让正在运行的线程进入阻塞状态,休眠时间满之后,进入就绪状态。
2. yield()方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。
sleep
- sleep,直接使用Thread.sleep(毫秒数);run方法中的sleep需要使用try catch捕获异常,不能抛出
- sleep示例,倒计时
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestBlock {
public static void main(String[] args) throws InterruptedException {
//倒计时的功能
Date d = new Date(System.currentTimeMillis()+10000);
long end = d.getTime();
while (true){
System.out.println(new SimpleDateFormat("hh:mm:ss").format(d));
Thread.sleep(1000);
d = new Date(d.getTime() - 1000);
if(end-10000>d.getTime()){
break;
}
}
}
static void test() throws InterruptedException {
int num = 10;
while (true){
System.out.println(num--);
Thread.sleep(1000);
if(num == 0){
System.out.println("点火...");
Thread.sleep(1500);
System.out.println("起飞");
break;
}
}
}
yield
- 直接进入就绪状态,让CPU调度
- 两个示例,直接使用Thread.yield();来暂停
public class TestYield {
public static void main(String[] args) {
//示例1
Myclass c = new Myclass();
new Thread(c,"1号线").start();
new Thread(c,"2号线").start();
//示例2 主线程礼让示例
//新线程
new Thread(()->{
for(int i = 0;i<100;i++ ){
System.out.println("lambda....."+i);
}
}).start();
//主线程
for(int i = 0;i<100;i++){
if(i % 10 == 0){
Thread.yield();
}
System.out.println("main..."+i);
}
}
}
class Myclass implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+"....start");
Thread.yield();
System.out.println(Thread.currentThread().getName()+".......end");
}
}
插队 join
- 线程A在运行期间,(B可能会执行),可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。联合之前,A和B可能都会执行一些。
- join方法通过对象调用
/**
* join示例
* 对象.join(毫秒);
*/
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
//join
//新线程
Thread t = new Thread(()->{
for(int i = 0;i<100;i++ ){
System.out.println("lambda....."+i);
}
});
t.start();
//主线程
for(int i = 0;i<100;i++){
if(i == 10){
//Thread.yield();
t.join(1); //i为10的时候让t开始,t结束之后再继续
}
System.out.println("main..."+i);
}
}
}
观察线程状态
- 五个状态 NEW,RUNNABLE,TIMED_WAITING,BLOCKED ,TERMINATED
- 观察五个状态
public class TestThreadState {
public static void main(String[] args) {
Thread thread = new Thread(() ->{
for(int i = 0;i<1;i++){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("---分线程执行结束----");
});
System.out.println(thread.getState()); //NEW
thread.start();
System.out.println(thread.getState()); //RUNNABLE
//监控整个过程的状态
while (true){
int num = Thread.activeCount(); //活动的线程数量
Thread.State state = thread.getState();
System.out.println(state+" ----- "+ num);
if(state.equals(Thread.State.TERMINATED)){ //如果分线程终止了,结束循环
break;
}
}
}
}
线程优先级
- 优先级高的执行概率大
- 处于就绪状态的线程,会进入“就绪队列”等待JVM来挑选。
- 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。int MIN_PRIORITY = 1,int NORM_PRIORITY = 5,int MAX_PRIORITY = 10
- 使用下列方法获得或设置线程对象的优先级。int getPriority(); void setPriority(int newPriority);
- 优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
public class TestPriority {
public static void main(String[] args) {
int a = Thread.currentThread().getPriority();
System.out.println(a);
MyThread t = new MyThread();
Thread t1 = new Thread(t,"线程一");
Thread t2 = new Thread(t,"线程二");
Thread t3 = new Thread(t,"线程三");
Thread t4 = new Thread(t,"线程四");
Thread t5 = new Thread(t,"线程五");
Thread t6 = new Thread(t,"线程六");
//设置优先级
t1.setPriority(1);
t2.setPriority(1);
t3.setPriority(1);
t4.setPriority(10);
t5.setPriority(10);
t6.setPriority(10);
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
int prioprity = Thread.currentThread().getPriority();
System.out.println(name+"优先级 ----> "+prioprity);
}
}
守护线程 daemon
- 用户线程和守护线程,守护线程为用户线程服务
- 用户线程执行完后,不需要等守护线程结束
- 默认是用户线程,通过Thread对象的setDaemon(true)将用户线程设置为守护线程
/**
* 将用户线程设置为守护线程
* 用户线程结束表示程序结束
*/
public class TestDaemon {
public static void main(String[] args) {
Person p = new Person();
Earth e = new Earth();
//将e设置为守护线程
Thread t = new Thread(e);
t.setDaemon(true);
t.start();
//p为用户线程
new Thread(p).start();
System.out.println(Thread.activeCount());
}
}
class Person implements Runnable{
@Override
public void run() {
for(int i = 0;i<100;i++){
System.out.println(i+1+"年过去了..");
}
System.out.println("一辈子结束了...");
}
}
class Earth implements Runnable{
@Override
public void run() {
while (true){
System.out.println("地球始终在转....");
}
}
}
常用方法
- isAlive是否活着,不是终止状态就是活着
- Thread.currentThread()表示当前线程
- setName设置线程名称,设置要在start之前
System.out.println(Thread.currentThread().isAlive());
Thread.currentThread().setName("hhh");
//Thread对象的setName
System.out.println(Thread.currentThread().getName());
线程同步
- 保证数据准确性和安全性,同时提高准确性
- 线程不安全的示例
import java.util.ArrayList;
import java.util.List;
public class TestUnsafe {
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());
}
}
- 可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块。
synchronized 方法
- 通过在方法声明中加入 synchronized关键字来声明,语法如下:public synchronized void accessVal(int newVal);
- synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
public class TestSynTicket implements Runnable {
private int tickets = 30;
public boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
public synchronized void buy(){
if(tickets==0){
flag = false;
System.out.println("无票");
return;
}
try {
Thread.sleep(100); //有网络延迟时,会出现负数
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 抢到一张,余票->"+ --tickets);
}
public static void main(String[] args) {
TestSynTicket tk = new TestSynTicket();
//一份资源多个线程争夺
new Thread(tk ,"1号线").start();
new Thread(tk,"2号线").start();
new Thread(tk,"3号线").start();
}
}
synchronized块
- synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
- Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
- 语法 synchronized(syncObject) { //允许访问控制的代码 }
- 票示例的同步块
public class TestSynTicket implements Runnable {
private int tickets = 30;
public boolean flag = true;
@Override
public void run() {
while (flag) {
buy5();
}
}
//同步方法
public synchronized void buy(){
if(tickets==0){
flag = false;
System.out.println("无票");
return;
}
try {
Thread.sleep(100); //有网络延迟时,会出现负数
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 抢到一张,余票->"+ --tickets);
}
//同步块,不能锁Integer Boolean,是变化的
//线程安全:尽可能锁定合理的范围(不是指代码 指数据的完整性)
//double checking
public void buy5() {
if(tickets<=0) {//考虑的是没有票的情况
flag = false;
return ;
}
synchronized(this) {
if(tickets<=0) {//考虑最后的1张票
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+tickets--);
}
}
public static void main(String[] args) {
TestSynTicket tk = new TestSynTicket();
//一份资源多个线程争夺
new Thread(tk ,"1号线").start();
new Thread(tk,"2号线").start();
new Thread(tk,"3号线").start();
}
}
- 容器的简单的同步示例
import java.util.ArrayList;
import java.util.List;
public class TestUnsafe {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for(int i = 0;i<100;i++){
new Thread(() -> {
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}
线程安全的ArrayList
- 直接用自带线程安全的ArrayList,即 java.util.concurrent.CopyOnWriteArrayList,使用与ArrayList相同
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestSynList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new CopyOnWriteArrayList<>();
for(int i = 0;i<1000;i++){
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}
死锁
- 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形
- 某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
- 死锁示例
public class TestDeadLock {
public static void main(String[] args) {
Markup g1 = new Markup(1,"罗密欧");
Markup g2 = new Markup(0,"朱丽叶");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Markup extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
//选择
int choice;
//名字
String girl;
public Markup(int choice,String girl) {
this.choice = choice;
this.girl = girl;
}
@Override
public void run() {
//化妆
markup();
}
//相互持有对方的对象锁-->可能造成死锁
private void markup() {
if(choice==0) {
synchronized(lipstick) { //获得口红的锁
System.out.println(this.girl+"涂口红");
//1秒后想拥有镜子的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/* //如果锁放在这里就会形成死锁
synchronized(mirror) {
System.out.println(this.girl+"照镜子");
}*/
}
//为了防止死锁需要把锁放到外面
synchronized(mirror) {
System.out.println(this.girl+"照镜子");
}
}else {
synchronized(mirror) { //获得镜子的锁
System.out.println(this.girl+"照镜子");
//2秒后想拥有口红的锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
synchronized(lipstick) {
System.out.println(this.girl+"涂口红");
} */
}
synchronized(lipstick) {
System.out.println(this.girl+"涂口红");
}
}
}
}
并发协作线程通信
管程法
- 多线程并发协作模型“生产者/消费者模式”。
- 生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。
- 消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。
- 消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
- 缓冲区是实现并发的核心,缓冲区的设置有3个好处:(1) 实现线程的并发协作: 有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。(2) 解耦了生产者和消费者,生产者不需要和消费者直接打交道。(3)解决忙闲不均,提高效率,生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据
- wait方法阻塞线程,会释放锁,使用notify唤醒,notifyAll唤醒全部
- 以生产馒头卖馒头为例,重要的部分在缓冲区
public class TestCo1 {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container=container;
}
@Override
public void run() {
for(int i = 0;i<100;i++){
System.out.println("生产-->"+i);
container.push(new Steambun(i));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("消费-->"+container.pop().id);
}
}
}
//缓冲区
class SynContainer{
Steambun[] buns = new Steambun[10];
int count = 0;
//存储
public synchronized void push(Steambun bun){
//生产时间,容器存在空间可以生产
if(count == buns.length){
//不能生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buns[count]=bun;
count++;
//存在数据,可以通知消费
this.notifyAll();
}
//获取消费
public synchronized Steambun pop(){
//消费的时机,判断容器中是否有数据
//没有数据就等待,调用wait方法
if(count == 0){
try {
this.wait(); //阻塞线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存在数据就可以消费
count--;
Steambun bun = buns[count];
this.notifyAll(); //存在空间,唤醒生产
return bun;
}
}
//馒头
class Steambun{
int id;
public Steambun(int id) {
this.id = id;
}
}
信号灯法
- 示例
public class TestCoRedLight {
public static void main(String[] args) {
Tv tv = new Tv();
new Player(tv).start();
new Watcher(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++){
if(i%2==0){
tv.play("小品");
}else{
tv.play("相声");
}
}
}
}
//消费者 观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv){
this.tv = tv;
}
@Override
public void run() {
for(int i=0;i<20;i++){
tv.watch();
}
}
}
//同一个资源 电视
class Tv{
String voice;
//true演员表演,观众等待,false观众观看,演员等待
boolean flag = true;
//表演
public synchronized void play(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表演
System.out.println("表演了.."+voice);
this.voice=voice;
this.notifyAll();
this.flag=!flag;
}
//观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("听到了.."+voice);
this.notifyAll();
this.flag=!flag;
}
}
/*
表演了..小品
听到了..小品
表演了..相声
听到了..相声
表演了..小品
听到了..小品
*/
高级操作
任务定时调度
- 使用java.util.Timer和TimerTask。TimerTask指定任务,Timer对任务进行时间调度
- 延迟执行任务,指定时间执行任务,每几秒运行一次,
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Timer;
import java.util.TimerTask;
/**
* 任务定时调度,借助Timer和TimerTask
*/
public class TestTimer {
public static void main(String[] args) {
Timer timer = new Timer();
//延迟一秒,执行一次进入等待
//timer.schedule(new MyTask(),1000);
//延迟5秒开始执行,以后每3秒执行一次
timer.schedule(new MyTask(),5000,3000);
//指定的时间执行,注意1月从0开始
//Calendar calendar = new GregorianCalendar(2020,1,22,9,30,0);
//timer.schedule(new MyTask(),calendar.getTime(),3000);
//2020-02-22 09:30:00
//2020-02-22 09:30:03
//2020-02-22 09:30:06
//2020-02-22 09:30:09
//System.out.println(calendar.getTime());
}
}
//任务类,run方法中写要执行的任务
class MyTask extends TimerTask{
@Override
public void run() {
for(int i = 0;i<1;i++){
//System.out.println("哈哈哈哈");
String t = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(System.currentTimeMillis());
System.out.println(t);
}
}
}
Quartz
- 在pom中添加依赖,https://mvnrepository.com/artifact/org.quartz-scheduler/quartz/2.3.2
- Scheduler调度器,控制所有的调度,Trigger触发条件,JobDetail需要处理的Job,Job执行逻辑
- 从官网下载http://www.quartz-scheduler.org/
- 直接从自带的示例代码修改
- 简单的示例,自带的示例1修改
//SimpleExample.java
import static org.quartz.DateBuilder.evenMinuteDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
public class SimpleExample {
public void run() throws Exception {
Logger log = LoggerFactory.getLogger(SimpleExample.class);
log.info("------- Initializing ----------------------");
// First we must get a reference to a scheduler
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
log.info("------- Initialization Complete -----------");
// computer a time that is on the next round minute
//下一分钟开始执行
Date runTime = evenMinuteDate(new Date());
log.info("------- Scheduling Job -------------------");
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
// Trigger the job to run on the next round minute
//每隔五秒执行一次,一共执行3次
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).
withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
log.info(job.getKey() + " will run at: " + runTime);
// Start up the scheduler (nothing can actually run until the
// scheduler has been started)
sched.start();
log.info("------- Started Scheduler -----------------");
// wait long enough so that the scheduler as an opportunity to
// run the job!
log.info("------- Waiting 65 seconds... -------------");
try {
// wait 65 seconds to show job
Thread.sleep(65L * 1000L);
// executing...
} catch (Exception e) {
//
}
// shut down the scheduler
log.info("------- Shutting Down ---------------------");
sched.shutdown(true);
log.info("------- Shutdown Complete -----------------");
}
public static void main(String[] args) throws Exception {
SimpleExample example = new SimpleExample();
example.run();
}
}
- 运行的任务
//HelloJob.java
import java.text.SimpleDateFormat;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job {
private static Logger _log = LoggerFactory.getLogger(HelloJob.class);
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("哈哈哈哈哈哈----"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(System.currentTimeMillis()));
_log.info("Hello World! - " + new Date());
}
}
happenbefore
- cpu根据重排指令对代码执行的顺序重排
- 例如下面的代码,当a==0的时候可能会输出a的值是1
public class TestHappenBefore {
private static int a=0;
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
// for(int i=0;i<100;i++){
// a=0;
// flag=false;
Thread t1 = new Thread(()->{
System.out.println("Thread1");
a=1;
flag=true;
});
Thread t2 = new Thread(()->{
System.out.println("Thread2");
if(flag){
a*=1;
}
if(a==0){
System.out.println("happen before a = "+a);
}
});
t2.start();
t1.start();
t1.join();
t2.join();
//}
}
}
volatile
- 保证线程间变量的可见性。线程对变量进行修改之后要立刻写会主内存,线程读取变量的时候从主内存读取,而不是缓存
- 保证数据的同步
public class TestVolatile {
//本例中,如果不加volatile修饰,就会死循环,加了后保证了数据的同步
private volatile static int num=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num==0){
//死循环
}
}).start();
Thread.sleep(1000);//1秒钟以后修改变量,while循环却没有终止
num=1;
}
}
单例模式
- 保证在多线程环境下,对外存在一个对象
- 1、构造器私有化 -->避免外部new构造器
- 2、提供私有的静态属性 -->存储对象的地址
- 3、提供公共的静态方法 --> 获取属性
public class SingleObj {
//私有静态属性
private static volatile SingleObj instance;
//构造器私有化
private SingleObj(){
}
//提供公共的静态方法
public static SingleObj getInstance(){
//减少不必要的同步
if(instance != null){
return instance;
}
//同步对象,防止重复创建对象,由于是静态方法,不能锁定this
synchronized (SingleObj.class){
//对象为空时创建对象
if(instance == null){
instance = new SingleObj();
}
}
return instance;
}
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(SingleObj.getInstance());
});
t.start();
System.out.println(SingleObj.getInstance());
}
}
ThreadLocal
- 线程自己的局部变量存储空间
- 多线程下保证成员变量的安全
- 使用private static修饰,常用方法get set initialValue
- 每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值
/**
* 每个线程都有自己的变量,不同线程间不影响
* 构造器被谁调用就是谁的线程
* InheritableThreadLocal能够获得调用者的threadlocal的值的副本
*/
public class TestThreadLocal {
//不指定初始值,默认为null
//private static ThreadLocal<Integer> t = new ThreadLocal<>();
//指定初始值
private static ThreadLocal<Integer> t1 = ThreadLocal.withInitial(()->{
return 200;
});
public static class MyRun implements Runnable{
public MyRun(){
t1.set(7); //修改的是main线程的
System.out.println(Thread.currentThread().getName()+"------"+t1.get());
}
@Override
public void run() {
//run中是开启的新线程
t1.set((int)(Math.random()*99)); //修改的是新线程
System.out.println(Thread.currentThread().getName()+"---"+t1.get());
}
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"---"+t1.get());//main线程的值
t1.set(10000); //设置值
System.out.println(Thread.currentThread().getName()+"---"+t1.get());//main线程的值
new Thread(new MyRun()).start();//线程1的值,MyRun构造的时候是main线程,调用run之后是新线程
new Thread(new MyRun()).start();//线程2的值
}
}
可重入锁
- 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞,不可重入锁则会阻塞。
- 示例
public class TestLock1 {
public void test(){
synchronized (this){
while (true){
synchronized (this){
System.out.println("Reintain lock");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
new TestLock1().test();
}
}
- 不可重入锁
/**
* 不可重入锁示例
*/
public class TestLock2 {
Lock lock = new Lock();
public void a() throws InterruptedException {
lock.lock();
doThing();
lock.unlock();
}
//不可重入锁
public void doThing() throws InterruptedException {
lock.lock();
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
TestLock2 t = new TestLock2();
t.a();
t.doThing();
}
}
class Lock{
private boolean isLocked = false;
//使用
public synchronized void lock() throws InterruptedException {
while (isLocked){
wait();
}
isLocked = true;
}
//释放
public synchronized void unlock(){
isLocked = false;
notify();
}
}
- 可重入锁,如果锁是当前线程的,继续使用当前锁,锁计数器加1,如果当前线程释放锁,锁的数量减1,但还是没有释放锁,当锁的数量为0的时候,就可以释放锁,其他的线程可以使用了
- 可重入锁的简单实现示例
/**
* 可重入锁示例
*/
public class TestLock3 {
ReLock lock = new ReLock();
public void a() throws InterruptedException {
lock.lock();
System.out.println("锁数量A "+lock.getHoldCount());
System.out.println("start");
doThing();
lock.unlock();
System.out.println("锁数量B "+lock.getHoldCount());
}
//不可重入锁
public void doThing() throws InterruptedException {
lock.lock();
System.out.println("锁数量C "+lock.getHoldCount());
System.out.println("hh");
lock.unlock();
System.out.println("锁数量D "+lock.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
TestLock3 t = new TestLock3();
t.a();
//t.doThing();
}
}
class ReLock{
//可重入锁
//是否占用
private boolean isLocked = false;
private Thread lockedBy = null; //哪个线程占用
private int holdCount = 0; //锁的数量
//使用
public synchronized void lock() throws InterruptedException {
Thread current = Thread.currentThread();
//如果锁被占用并且是其他线程,那么等待
while (isLocked && lockedBy != current){
wait();
}
isLocked = true;
lockedBy = current;
holdCount ++;
}
//释放
public synchronized void unlock(){
//当前线程的时候释放
if(Thread.currentThread() == lockedBy){
holdCount--; //锁数量减去1
//数量为0的时候就释放
if(holdCount==0){
isLocked = false;
notify();
lockedBy = null;
}
}
}
//锁数量的getter方法
public int getHoldCount() {
return holdCount;
}
}
- 自带的可重入锁使用,lock和unlock方法,getHoldCount获取锁的数量等
public class TestLock4 {
ReentrantLock lock = new ReentrantLock();
public void a() throws InterruptedException {
lock.lock();
System.out.println("锁数量A "+lock.getHoldCount());
System.out.println("start");
doThing();
lock.unlock();
System.out.println("锁数量B "+lock.getHoldCount());
}
//不可重入锁
public void doThing() throws InterruptedException {
lock.lock();
System.out.println("锁数量C "+lock.getHoldCount());
System.out.println("hh");
lock.unlock();
System.out.println("锁数量D "+lock.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
TestLock4 t = new TestLock4();
t.a();
//t.doThing();
}
}
CAS乐观锁
- 乐观锁。每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。乐观锁适合多读的场景,实现思想有version方式和CAS方式。
- 悲观锁 :每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。在Java中,synchronized的思想也是悲观锁。
- java.util.concurrent.atomic.* 其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入
import java.util.concurrent.atomic.AtomicInteger;
public class TestCAS {
private static AtomicInteger num = new AtomicInteger(5);
public static void main(String[] args) {
for(int i = 0;i<10;i++){
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Integer left = num.decrementAndGet();
if(left<1){
System.out.println(Thread.currentThread().getName()+"-----卖完了");
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了一个,还剩:"+left);
}).start();
}
}
}
附 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.king</groupId>
<artifactId>testjava</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>