1简介
1.正常的程序执行时纵向顺序执行。如果主线程调用了其他方法,则是在其他方法执行完成之后再回到主线程。
2.多线程执行,则是在主线程执行过程中新开一条线程执行其他方法,这个时候主线程和子线程并行交替执行。
3.Process程序跑起来就是一个进程,每一个进程包含多个线程thread
4.很多多线程都是模拟出来的,就是只有一个cpu交替执行不同的线程。并不是正真意义上的同时执行,只有多个cpu才能实现多线程。
2基本概念
1.线程就是独立的执行路径(每个线程自己执行,如main,gc).
2.在程序执行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
3.main(),称之为主线程,为系统的入口,用于执行整个程序。
4.在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。cpu调度执行,了解线程优先级的概念
5.对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。 必须了解的高并发问题
6.线程会带来额外的开销,如cpu调度事件,并发控制开销。就是为了让多个线程或程序同时执行,cpu会消耗时间。
7.每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。。。。。。。原子性
3线程创建
1.继承Thread类,实现Runnable接口,实现Callable接口
1.继承Thread
//这个是继承Thread类来实现多线程的方法
//线程开启后不一定立即执行,需要等待cpu调度决定什么时候执行
public class TestThread01 extends Thread{
//重写run();
@Override
public void run(){
for (int i = 0; i < 200; i++) {
System.out.println("===我在执行多线程"+i);
}
}
public static void main(String[] args) {
TestThread01 testThread01= new TestThread01();
//调用start()方法才会开启新的线程,
//testThread01.start();
//调用run()方法,只是普通调用方法,不会有新的线程
testThread01.run();
for (int i = 0; i < 200; i++) {
System.out.println("===我在执行主方法"+i);
}
}
}
我的实际工作中,导入数据并持久化用到过多线程。主线程调用子线程,实现对数据的校验和持久化操作(有异常则抛出异常),主线程则返回数据给前端,表示数据已经上传成功。
一个简单的多线程下载图片的:通过继承Threa接口
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread2 extends Thread{
private String url;
private String name;
//构造方法
public TestThread2(String url,String name ){
this.url=url;
this.name=name;
};
@Override
public void run() {
//新的线程调用的下载方法
WebDownLoad webDownLoad = new WebDownLoad();
webDownLoad.webDownLoad( url, name);
System.out.println("下载图片出错了"+name);
}
public static void main(String[] args) {
TestThread2 testThread2 = new TestThread2("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","first.png");
TestThread2 testThread3 = new TestThread2("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","second.png");
TestThread2 testThread4 = new TestThread2("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","third.png");
testThread2.start();
testThread3.start();
testThread4.start();
}
}
class WebDownLoad{
public void webDownLoad(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File (name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载图片出错了"+Thread.currentThread());
}
}
}
2.实现Runnable接口,多用该方法比较灵活
通过实现Runnable接口,然后是静态代理的方式新建Thread调用start()
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread2 implements Runnable{
private String url;
private String name;
public TestThread2(String url,String name ){
this.url=url;
this.name=name;
};
@Override
public void run() {
WebDownLoad webDownLoad = new WebDownLoad();
webDownLoad.webDownLoad( url, name);
}
public static void main(String[] args) {
TestThread2 testThread2 = new TestThread2("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","first.png");
TestThread2 testThread3 = new TestThread2("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","second.png");
TestThread2 testThread4 = new TestThread2("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","third.png");
new Thread(testThread2).start();
new Thread(testThread3).start();
new Thread(testThread4).start();
}
}
class WebDownLoad{
public void webDownLoad(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File (name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载图片出错了");
}
}
}
2.1多各线程同时操作一个对象课程出现的并发问题。
public class ThreadRunnables implements Runnable{
private int ticket =10;
@Override
public void run() {
while (true){
if(ticket<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket-- +"张票");
}
}
public static void main(String[] args) {
ThreadRunnables threadRunnables = new ThreadRunnables();
//多个线程同时对一个对象进行操作
new Thread(threadRunnables,"小哥").start();
new Thread(threadRunnables,"同学").start();
new Thread(threadRunnables,"小红").start();
new Thread(threadRunnables,"路边打印").start();
}
}
多线程并发的明显结果:同时出现了相同的票号,而却居然有负数的票
同学拿到了第10张票
路边打印拿到了第9张票
小红拿到了第9张票
小哥拿到了第10张票
同学拿到了第8张票
路边打印拿到了第7张票
小红拿到了第6张票
小哥拿到了第5张票
小哥拿到了第4张票
小红拿到了第4张票
路边打印拿到了第3张票
同学拿到了第2张票
路边打印拿到了第1张票
小红拿到了第-1张票
小哥拿到了第0张票
同学拿到了第0张票
2.2龟兔赛跑的多线程操作。对于多线程有了更深入的认识。
启动多线程调用同一个Runnable对象。在这个时候,Runnable的属性参数是多个线程公用的,只要有一个线程发生了变化,其他线程也发生了变化。run()方法是每个线程单独执行了,用到的Runnable参数会存在并发问题,其他新的变量都是各自变化的。
public class Race implements Runnable{
private String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if ( gameOver(i)){
if(Thread.currentThread().getName().equals("兔子") && i/10==0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i);
}
};
private boolean gameOver(int step){
if(step==100 && winner == null){
winner = Thread.currentThread().getName();
System.out.println(Thread.currentThread().getName()+"取得胜利");
return true;
}else if (step==100 && winner != null){
System.out.println(Thread.currentThread().getName()+"也结束了比晒");
return true;
}{
return false
;
}
}
public static void main(String[] args) {
Race race=new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
3实现Callable接口
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
5.提交执行:Future result1 = ser.submit(t1);
6.获取结果:boolean r1 = result1.get();
7.关闭服务:set.shutdownNow();
3.1实现callable接口,多线程下载图片(改造自实现Runnable接口)
public class ThreadCallable implements Callable<Boolean> {
private String url;
private String name;
public ThreadCallable(String url,String name ){
this.url=url;
this.name=name;
};
@Override
public Boolean call() throws Exception {
WebDownLoad webDownLoad = new WebDownLoad();
webDownLoad.webDownLoad( url, name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadCallable testThread2 = new ThreadCallable("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","first.png");
ThreadCallable testThread3 = new ThreadCallable("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","second.png");
ThreadCallable testThread4 = new ThreadCallable("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png","third.png");
//创建服务
ExecutorService ser= Executors.newFixedThreadPool(3);3为连接数量
//提交执行
Future<Boolean> r1 = ser.submit(testThread2);
Future<Boolean> r2 = ser.submit(testThread3);
Future<Boolean> r3 = ser.submit(testThread4);
//获取结果
boolean re1 = r1.get();
boolean re2 = r2.get();
boolean re3 = r3.get();
ser.shutdownNow(); //关闭服务
}
}
class WebDownLoad{
public void webDownLoad(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载图片出错了");
}
}
}
4 Thread类实现了Runnable接口
在我们通过实现Runnable接口的方法来实现多线程,最后通过new Thread(Runnable ru).start()方法来开启新的线程,启动run()方法的时候,用了静态代理的模式。Thread其实就是那个代理类。
4Lambda表达式
-
理解Function Interface 函数式接口是学习java8 lambda表达式的关键
-
函数式接口的定义:
1.对于任何一个接口,如果只包含一个抽象方法,那么它就可以称之为函数式接口
2.对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
以下是从一个函数式接口和实现类到lambda表达式的形成过程(必须是函数式接口)
1.一般类实现接口
2.静态内部类实现
3.局部内部类实现
4.匿名内部类实现
5.lambda表达式
package lambda;
public class TestLambda {
//类里的类静态内部类
static class ILike2 implements Like{
@Override
public void lambada() {
System.out.println("i like labmda2");
}
}
public static void main(String[] args) {
Like like=new ILike();
like.lambada();
like=new ILike2();
like.lambada();
//局部内部类
class ILike3 implements Like{
@Override
public void lambada() {
System.out.println("i like labmda3");
}
}
like = new ILike3();
like.lambada();
//匿名内部类。只有接口对象
like = new Like(){
@Override
public void lambada() {
System.out.println("i like labmda4");
}
};
like.lambada();
like = ()-> {
System.out.println("i like labmda5");
};
like.lambada();
Show show = ()->{
System.out.println("i show");
};
show.showMe();
}
}
interface Like{
void lambada();
}
class ILike implements Like{
@Override
public void lambada() {
System.out.println("i like labmda1");
}
}
interface Show{
void showMe();
}
interface ILove{
void MyLove(String name,int age)
}
//带有参数的lambda表达式
ILove ilove = (name,age)->{
System.out.pring(name+"小哥:"+age);
}
5线程的状态
6线程常用的方法
1.setPriority(int newPriority);更改线程优先级
2.static void sleep(long mills);指定毫秒时间内休眠,结束后就绪
3.void join(); 插队,等待该线程终止?????
4.static void yield() ;暂停当前正在执行的线程,并执行其他线程
5.void interrupt(); 中断线程,别用这个方法!!!
6.Boolean isAlive(); 测试线程是否在存活状态。
6.1 线程的终止
package TestThread;
import sun.awt.windows.ThemeReader;
//测试stop
//1.建议线程正常停止,---》利用次数,不建议死循环
//2.建议使用标志位,---》设立一个线程停止的标志
//3.不要使用stop,destory等过时的或者jdk不建议使用的方法
public class TestStop implements Runnable{
//设立一个停止的标记
private boolean flag =true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("子线程在执行"+i++);
}
/* try {
Thread.sleep(3000);
System.out.println("子线程在执行"+flag);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println("子线程在执行>>>"+Thread.currentThread().isAlive());
}
//写一个公开的方法停止线程。就是让子线程的程序结束
public void stop(){
this.flag=false;
};
public static void main(String[] args) {
TestStop stop = new TestStop();
new Thread(stop,"自线程").start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程执行"+i);
if(i==89){
stop.stop();
}
}
}
}
6.2 线程休眠
一般用于模拟网络延时或者倒计时
6.2.1 sleep是指当前线程阻塞的毫秒数
6.2.2 sleep存在异常InterruptedException
6.2.3sleep时间到达后,线程进入就绪状态
6.2.4可以用于模拟网络延时或者倒计时。。。
6.2.5没一个对象都有一个锁,sleep不会释放锁。
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
/*for (int i = 0; i < 10; i++) {
System.out.println(i);
Thread.sleep(1000);
}*/
Date date = new Date(System.currentTimeMillis());
while (true){
Thread.sleep(1000);//线程休眠一秒钟
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
}
}
}
6.3线程礼让
这个比较简单,就是在代码中调用以下方法:: Thread.yeild();
6.4线程join
在一个线程执行过程中,另外一个线程join进来,cpu会优先执行这个线程,在这个线程执行结束后再回来
1.当两个线程在执行,jion的线程执行完之后,被插入的线程就绪
2.如果多个线程在执行,被jion的线程阻塞,其他线程正常的就绪状态
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin,"VIP");
TestJoin testJoin1 = new TestJoin();
Thread thread1 = new Thread(testJoin1,"普通用户");
thread.start();
thread1.start();
for (int i = 0; i < 200; i++) {
System.out.println("主线程在跑"+i);
if(i==100){
thread.join();
}
}
}
}
6.5线程状态获取thread.getState();
1.新生 2.就绪 3.阻塞 4.运行 5.死亡
1.Thread t = new Thread();线程对象一旦创建就进入新生状态。
Thread.State state= thread.getState(); NEW state是枚举值
2.就绪 t.start();,调用start后,线程进入就绪状态,但是不一定立即执行,RUNNABKLE
3.阻塞,当调用sleep,wait或同步锁时,线程进入阻塞状态。代码不往下执行,阻塞解除后,重新进入就绪状态,等待CPU分配。 BLOCKED
4.进入运行状态的线程才正在执行程序代码
5.线程中断或者结束,一旦进入死亡状态不可以再次启动。 TERMINATED
6.6 线程优先级
优先级范围1-10,最小为1,组大为10,设置其他数值则抛异常。
必须先设置优先级再启动线程才会有效。
Thread.MAX_PRIORITY 最大为10
Thread.NORM_PRIORITY 默认为5
Thread.MIN _PRIORITY 最小为1,
thread1.setPriority(1);
thread1.start();
Thread.currentThread().getPriority() 获得当前线程的优先级
6.7守护线程 thread.setDaemon
1.线程分为守护线程和用户线程
2.我们正常建立的线程都是用户线程
3.虚拟机必须确保用户线程执行完毕
4.虚拟机不用等待守护线程执行完毕
5.守护线程:如后台记录操作日志,监控内存,垃圾回收等
thread.setDaemon(true);设置为守护线程
package TestThread;
public class TestDaemon {
public static void main(String[] args) {
God god =new God();
Thread thread = new Thread(god);
thread.setDaemon(true);
thread.start();
new Thread(new You()).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("s" +
"上帝在守护你");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("我快乐的或者!!");
}
}
}
7不安全的多线程
7.1 多线程买票
7.2 多线程取钱
7.3ArrayList<>()
8同步方法同步块
8.1同步方法
同步方法,及在方法前加 synchronized: public synchronized void method(args[]){};
//同步方法,保证线程安全,这里锁的时当前方法所在的类对象。
public synchronized void buyticket() throws InterruptedException {
//判断是否有票
if(ticketNum<=0){
System.out.println("票卖光了");
flag=false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"购买了第"+ticketNum--+"张票");
}
8.2同步块
同步块,即在一端代码前加上锁 synchronized(obj){};
obj是锁的对象,{}是代码块,只有获取了obj的对锁才能执行代码块里的代码
{
synchronized(account){//被锁的是取钱的账户。
if(account.getMoney()<dmoney){
System.out.println("钱不够了!!!");
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setMoney(account.getMoney()-dmoney);
System.out.println(account.getMoney());
}
}
8.3一个安全的list
CopyOnWriteArrayList 是一个线程安全的集合
{
CopyOnWriteArrayList<String> copyOnWriteArrayList =new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
copyOnWriteArrayList.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(2000);
System.out.println(copyOnWriteArrayList.size());
}
8.4死锁
产生死锁的四个必要条件:
1.一个资源一次只能被一个进程私用。
2.请求与保持条件:一个进程因请求资源阻塞时,对已获得的资源保持不放
3.不剥夺条件:进程已获得的资源,在未使用完之前不能强行剥夺
4.循环等条件:若干进程之间形成循环等资源的关系
public class TestDeadLock {
public static void main(String[] args) {
new Thread(new MakeUp(0,"韩晓晓")).start();
new Thread(new MakeUp(2,"韩大大")).start();
}
}
class Mirror{
}
class LipTick{
}
class MakeUp extends Thread{
static Mirror mirror =new Mirror();
static LipTick lipTick = new LipTick();
int nu;
String name;
public MakeUp(int nu, String name ){
this.name=name;
this.nu=nu;
}
@Override
public void run() {
if(nu==0){
synchronized (mirror){
System.out.println(name+"拿到了镜子");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipTick){
System.out.println(name+"拿到了口红");
}
}else{
{
synchronized (lipTick){
System.out.println(name+"拿到了口红");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror){
System.out.println(name+"拿到了镜子");
}
}
}
}
8.5 LOCK显式锁
1.定义一个锁 private final ReentrantLock lock = new ReentrantLock();
2.听过try{}形式,锁定一个代码块,lock.lock 和lock.unlock
class BuyTicket implements Runnable{
private int num =10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + num--);
} else {
break;
} ;
}finally {
lock.unlock();
}
}
}}
9管程法
生产者生产产品放入缓存区,消费者从缓冲区取产品。
//生产者,消费者模型,利用缓冲区----管程法让两个线程关联
//生产者,消费者,产品,缓冲区
public class TestPc {
public static void main(String[] args) {
SynContainer synContainer =new SynContainer();
new Product(synContainer).start();
new Customer(synContainer).start();
}
}
产品
class Chicken{
public int id;
public Chicken(int id){
this.id=id;
};
}
缓冲区
class SynContainer{
Chicken[] cs = new Chicken[10];
int count=0;
public synchronized void push(Chicken chicken) throws InterruptedException {
if(count == cs.length){
this.wait();
}
System.out.println("前台来了一只鸡"+chicken.id);
cs[count]=chicken;
count++;
this.notifyAll();
}
public synchronized Chicken pop() {
if(count<=0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
Chicken chicken = cs[count];
System.err.println("消费着消费一只鸡"+chicken.id);
this.notifyAll();
return chicken;
}
}
消费者
class Customer extends Thread{
SynContainer synContainer;
public Customer(SynContainer synContainer){
this.synContainer=synContainer;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(500);
synContainer.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者
class Product extends Thread{
SynContainer synContainer;
public Product(SynContainer synContainer){
this.synContainer=synContainer;
}
@Override
public void run() {
for (int i1 = 0; i1 < 100; i1++) {
try {
Thread.sleep(200);
Chicken chicken =new Chicken(i1);
synContainer.push(chicken);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
10信号等法
通过一各标记,控制两个线程的等待或者开启
package TestThread;
public class TestPC2 {
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 < 100; i++) {
if(i%2==0){
tv.payer("一首歌春天的故事");
}else {
tv.payer("表演了电视据红玫瑰");
}
}
}
}
//消费者
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.watcher();
}
}
}
//产品
class TV {
String voice;
//开关
boolean flag = true;
public synchronized void payer(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.voice=voice;
System.out.println("演员表演:"+voice);
flag = !this.flag;
this.notifyAll();
}
public synchronized void watcher(){
if (flag ){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看表演:"+voice);
flag=!flag;
this.notifyAll();
}
}
11线程池
public class TestThreadPools {
public static void main(String[] args) {
//生成一个有两个线程的池
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new threads());//开启线程服务
service.execute(new threads());
service.execute(new threads());
service.execute(new th());
service.execute(new th());
service.execute(new th());
//关闭线程
service.shutdown();
}
}
class threads implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
class th extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"Run");
}
}