多线程
程度、进程、线程
程序:程序是指令和数据的有序集合。其本身没有任何运行的含义。是一个静态的概念。
进程:进程是执行程序的一次执行过程,他是一个动态的概念。是系统资源分配的单位。
**(重点)**线程:通常是一个进程中包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
-
线程就是独立的执行路径
-
在程序运行时,即使没有自己创建线程。后台也会有多个线程,如主线程,gc线程(垃圾回收线程);
-
main()称之为主线程,为系统的入口,用于执行整个程序。
-
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
-
对同一份资源操作时,会存在资源的抢夺问题,需要加入并发控制。
-
线程会带来额外的开销,如CPU调度时间,并发控制开销。
-
每个线程在自己的工作内存交互,内存控制不当会造成数据的不一致。
线程的创建
- 继承Thread类
- 实现Runable接口
- 实现Callable接口(了解)
Thread线程的创建
package Thread;
/**
* 创建Thread线程的方法
* 1、继承Thread类
* 2、重写run()方法
* 3、创建ThreadDemo01实例化对象
* 4、调用start()方法
*/
public class ThreadDemo01 extends Thread{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("你好世界");
}
}
public static void main(String[] args) {
ThreadDemo01 threadDemo01=new ThreadDemo01();
threadDemo01.start();
for (int i = 0; i < 1000; i++) {
System.out.println("Hello Worl");
}
}
}
网图下载
package PictureDownload;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//创建多线程
public class PictureDownloadDemo01 extends Thread {
String url;
String name;
public PictureDownloadDemo01(String url,String name){
this.url=url;
this.name=name;
}
//重新run方法(线程的实现类)
@Override
public void run() {
Downloader downloader=new Downloader(url,name);
}
//创建main的主方法
public static void main(String[] args) {
PictureDownloadDemo01 pictureDownloadDemo01=new PictureDownloadDemo01("https://www.sdmu.edu.cn/__local/C/EE/61/6B361A85DABD8C57D25917926AB_8316D48A_12525.png","name1.png");
PictureDownloadDemo01 pictureDownloadDemo011=new PictureDownloadDemo01("https://www.sdmu.edu.cn/images/111.jpeg","name2.jpeg");
PictureDownloadDemo01 pictureDownloadDemo012=new PictureDownloadDemo01("https://www.sdmu.edu.cn/images/mmexport1585274129719.jpg","name3.jpg");
pictureDownloadDemo01.start();
pictureDownloadDemo011.start();
pictureDownloadDemo012.start();
}
}
//创建下载器
class Downloader {
public Downloader( String url ,String name) {
//创建文件的下载方法,获取文件的url并进行name命名
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
实现Runnable接口
package Runnable;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class RunableDemo01 implements Runnable {
String url;
String name;
public RunableDemo01(String url , String name) {
this.name=name;
this.url=url;
}
//多线程的执行方法
@Override
public void run() {
DownLoader downLoader=new DownLoader(url,name);
System.out.println(""+name);
}
public static void main(String[] args) {
RunableDemo01 runnableDemo01=new RunableDemo01("https://www.sdmu.edu.cn/__local/C/EE/61/6B361A85DABD8C57D25917926AB_8316D48A_12525.png","1.png");
RunableDemo01 runnableDemo012=new RunableDemo01("https://www.sdmu.edu.cn/images/111.jpeg","2.jpeg");
RunableDemo01 runnableDemo013=new RunableDemo01("https://www.sdmu.edu.cn/images/mmexport1585274129719.jpg","3.jpg");
Thread thread=new Thread(runnableDemo01);
Thread thread1=new Thread(runnableDemo012);
Thread thread2=new Thread(runnableDemo013);
thread.start();
thread1.start();
thread2.start();
}
}
//构建下载器
class DownLoader{
public DownLoader( String url,String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结
继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承的局限性。
实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活多变,方便同一个对象被多线程使用。
初识并发问题
package BUY;
public class BuyDemo01 implements Runnable {
int trainTickets=10;
int num=0;
@Override
public void run() {
while(true){
//设置延时。
//设置网络延时作用:放大问题的发生性
//查看代码中的错误
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//设置循环结束的条件
if(trainTickets<=0){
break;
}
//Thread.currentThread().getName()可以获取线程的信息,获得线程的名字
System.out.println(Thread.currentThread().getName()+"得到了第"+trainTickets-- +"票");
num++;
}
System.out.println("一共进行了"+num+"次");
}
public static void main(String[] args) {
BuyDemo01 buyDemo01=new BuyDemo01();
Thread thread=new Thread(buyDemo01,"小明");
Thread thread2=new Thread(buyDemo01,"小红");
Thread thread3=new Thread(buyDemo01,"黄牛");
Thread thread4=new Thread(buyDemo01,"小张");
thread.start();
thread2.start();
thread3.start();
thread4.start();
}
}
龟兔赛跑
package Rabbit;
public class RabbitDemo01 implements Runnable {
String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
String name;
name=Thread.currentThread().getName();
//设置延缓时间来模仿兔子睡觉
if(name=="兔子"&& i%10==0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断是否进行种植循环
Boolean flag=MyRabbit(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName() + "走了" + i + "步");
}
}
//进行冠军的判断
public boolean MyRabbit(int step){
//判断冠军是否有人。
if(winner!=null){
return true;
}else{
//判断行走的距离
if(step>=100){
winner=Thread.currentThread().getName();
System.out.println("winner is"+Thread.currentThread().getName());
return true;
}
}
return false;
}
//实现线程
public static void main(String[] args) {
RabbitDemo01 rabbitDemo01 = new RabbitDemo01();
Thread thread = new Thread(rabbitDemo01, "兔子");
Thread thread1 = new Thread(rabbitDemo01, "乌龟");
thread.start();
thread1.start();
}
}
静态代理模式
package StaticReplace;
public class StaticReplaceDemo01 {
public static void main(String[] args) {
Actual actual=new Actual();
Replace replace=new Replace(actual);
replace.Marry();
}
}
//创建一个总的接口
interface StaticReplaceDemo02 {
public void Marry();
}
//真实的对象连接接口
class Actual implements StaticReplaceDemo02{
@Override
public void Marry() {
System.out.println("进行项目的验收,项目完成良好,非常高兴");
}
}
//代理对象进行接口的关联
//静态代理模式中主要的实现方法大部分都是在代理类中实现的。
class Replace implements StaticReplaceDemo02{
//获取Actual进行代理调用
private StaticReplaceDemo02 name;
public Replace(StaticReplaceDemo02 name){
this.name=name;
}
@Override
public void Marry() {
before();
this.name.Marry();//进行调用Actual的Marry方法;
after();
}
public void before(){
System.out.println("进行项目合同的签订");
}
public void after(){
System.out.println("收付尾款");
}
}
Lamda表达式
核心
理解Functional Interface(函数式接口)是学习java8lambda 表达式的关键所在。
函数式接口的定义
- 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口
- 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
线程的状态
线程的方法
- setPriority(int newPriority):更改线程的优先级
- static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠
- void join():等待该线程终止
- static void yield():暂停当前正在执行的线程对象,并执行其他的线程
- void interrupt():中断线程,别用这个方式
- boolean isAlive():测试线程是否处于活动状态
注:线程的终止建议的是设置一个标志位,通过某种条件来判断线程是否需要终止。
线程的停止
package Stop;
public class StopDemo01 implements Runnable{
Boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
if(flag){
System.out.println(Thread.currentThread().getName()+"运行了"+ i++ +"次");
}
}
}
//设置判断条件的修改方法
public void MyStopDemo01(){
this.flag=false;
}
public static void main(String[] args) {
//进行线程的启动
StopDemo01 stopDemo01=new StopDemo01();
Thread thread=new Thread(stopDemo01,"小明");
thread.start();
for(int i=0;i<1000;i++){
System.out.println("main运行了"+i);
//设置线程终止的判断条件
if (i==900){
System.out.println("小明运行结束");
stopDemo01.MyStopDemo01();
}
}
}
}
sleep模拟倒计时和获取时间
package Sleep;
import java.util.Date;
public class SleepDemo01 {
public static void main(String[] args) {
while (true) {
try {
Thread.sleep(1000);//设置时间的更新间隔
Date data=new Date(System.currentTimeMillis());//获取系统的当前时间
System.out.println("现在的时间是"+data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让_yield
- 线程的礼让,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转化为就绪状态
- 让CPU重新调度,礼让不一定成功!看CPU的心情
- 礼让不一定成功,最终的结果查看CPU的调度。
package yield;
public class YieldDemo01 {
public static void main(String[] args) {
MyYielDemo01 myYielDemo01=new MyYielDemo01();
Thread thread=new Thread(myYielDemo01,"a");
Thread thread1=new Thread(myYielDemo01,"b");
thread.start();
thread1.start();
}
}
class MyYielDemo01 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始运行了");
//进项进行线程的礼让。
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程开始运行了");
}
}
线程的强制执行_join
Join合并线程,待此线程执行完成后,在执行其他的线程,否则其他线程阻塞。
package Join;
public class JoinDemo01 implements Runnable {
@Override
public void run() {
for (int i=1;i<=1000;i++){
System.out.println("我是vip,我要进行插队"+i);
}
}
public static void main(String[] args) throws InterruptedException {
JoinDemo01 joinDemo01=new JoinDemo01();
Thread thread=new Thread(joinDemo01);
thread.start();
for (int i = 0; i < 500; i++) {
System.out.println("普通线程"+i);
//进行强制运行run中的循环结构
if(i==200){
thread.join();//天剑join方法,阻断main方法中的循环体
}
}
}
}
线程状态的检测
package State;
public class StateDemo01 implements Runnable {
static int i;
@Override
public void run() {
for ( int a = 0; a <5; a++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.i=a;
}
System.out.println("线程状态监测结束");
}
public static void main(String[] args) {
StateDemo01 stateDemo01=new StateDemo01();
Thread thread=new Thread(stateDemo01);
System.out.println("线程的状态显示");
//启动线程
thread.start();
while (true){
//获取线程的状态
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.State state=thread.getState();
System.out.println(state);
if (i==4){
break;
}
}
}
}
线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行。
线程的优先级用数字来表示,范围从1~10.
- Thread.MIN_PRIORITY=1;
- Thread.MAX_PRIORITY=10;
- Thread.NORM_PRIORITY=10;
使用下列方式改变和获取优先级
- getPriority().setPriority(int xxx);
优先级低只是意味着获取调度的概率低,并不是优先级低的就不会被调用。这都是看CPU的调度
package Priority;
public class PriorityDemo01 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriorityDemo01 myPriorityDemo01=new MyPriorityDemo01();
Thread thread=new Thread(myPriorityDemo01,"a");
Thread thread1=new Thread(myPriorityDemo01,"b");
Thread thread2=new Thread(myPriorityDemo01,"c");
Thread thread3=new Thread(myPriorityDemo01,"d");
Thread thread4=new Thread(myPriorityDemo01,"e");
thread.setPriority(9);
thread.start();
thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.setPriority(6);
thread2.start();
thread3.setPriority(7);
thread3.start();
thread4.setPriority(8);
thread4.start();
}
}
class MyPriorityDemo01 implements Runnable{
@Override
public void run() {
Thread thread=new Thread();
System.out.println(Thread.currentThread().getName()+"-->"+thread.getPriority());
}
}
各个线程的默认值
注:其实线程的优先级设置可以理解为线程抢占CPU时间片的概率,虽然概率比较大,但是它不一定就是按照优先级的顺序去抢占CPU时间片的,具体的执行顺序还是要根据谁先抢到了CPU的时间片,谁就先来执行。
因此千万不要把设置线程的优先顺序当做是线程实际启动的优先顺序哦!
守护线程Daemon
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待。
package Deamon;
/**
* 测试守护线程
*/
public class DaemonDemo01 {
public static void main(String[] args) {
loverDemo01 loverDemo01=new loverDemo01();
GodDemo01 godDemo01=new GodDemo01();
Thread thread=new Thread(godDemo01);
//设置God这个线程为守护线程
thread.setDaemon(true);//默认值是false,用户线程,true表示的是守护线程
thread.start();
Thread thread1=new Thread(loverDemo01);
thread1.start();
}
}
class GodDemo01 implements Runnable{
@Override
public void run() {
while (true){
int i=1;
System.out.println("守护" + i++);
}
}
}
class loverDemo01 implements Runnable{
@Override
public void run() {
for (int i = 1; i <36500 ; i++) {
System.out.println("生命中的第"+i+"天");
}
}
}
注:守护线程会随着其余线程的执行完毕,而完毕。
线程的同步
线程同步发生的情况一般是在多个线程操作一个资源的时候。
并发:同一个对象被多个线程操作、
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程的竞争下,加锁,释放锁会导致比较多的上下文切换和调度的延时,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
同步方法及同步块
- 由于我们的可以通过private关键字来保证数据对象只能被方法访问。所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,他包括两种方法:synchronized方法和synchronized块
同步方法:public synchronized void method(int args){}
- synchronized方法控制“对象”的访问,每个对象对应的一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized,将会影响效率
同步块
同步块synchronized(obj){} obj:同步监视器
- obj可以是任何对象,但是推荐使用共享资源作为监视对象
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中的代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
死锁
package Lock;
public class LockDemo01 {
public static void main(String[] args) {
MyLockDemo01 myLockDemo01=new MyLockDemo01(0,"a");
MyLockDemo01 myLockDemo011=new MyLockDemo01(1,"b");
myLockDemo01.start();
myLockDemo011.start();
}
}
//获取镜子
class Mirror {
}
//获取口红
class Lipstick {
}
class MyLockDemo01 extends Thread{
static Mirror mirror=new Mirror();
static Lipstick lipstick=new Lipstick(); //必须加static,否则不会出现下图结果
int chock;
String name;
@Override
public void run() {
try {
testDemo01();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public MyLockDemo01(int chock, String name){
this.chock=chock;
this.name=name;
}
public void testDemo01() throws InterruptedException {
if(chock==0){
synchronized (mirror){
System.out.println(name+"获得了镜子");
Thread.sleep(1000);
synchronized (lipstick){
System.out.println(name+"获取口红");
}
}
}else {
synchronized (lipstick){
System.out.println(name+"获取口红");
Thread.sleep(2000);
synchronized (mirror){
System.out.println(name+"获取镜子");
}
}
}
}
}
Lock锁
- JDK5.0开始,JAVA提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用lock充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了队共享资源包的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制之中,比较常用的是ReentrantLock,可以显示加锁、释放锁。