进程、线程
概念:
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。
一个程序至少一个进程,一个进程至少一个线程。
创建线程
方法一:自定义线程类继承thread类
1,自定义线程类继承thread类
2,重写run方法,编写线程执行体
3,创建thread类的子类的对象。即刚定义的线程类
4,调用start方法启动线程
//示例代码:实现一个多线程同步下载图片
package L6;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//1,自定义线程类TestThread2继承thread类
public class TestThread2 extends Thread{
private String UrlName;
private String filename;
public TestThread2(String UrlName,String filename){
this.filename=filename;
this.UrlName=UrlName;
}
//2,重写run方法
//下载图片线程的执行体
public void run(){
WebDownloader w1=new WebDownloader();
w1.Downloader(UrlName,filename);
System.out.println("下载的文件名为"+filename);
}
public static void main(String[] args) {
//3,创建thread类的子类的对象。
TestThread2 T1=new TestThread2("https://pic1.zhimg.com/80/v2-6257d94a8a01c48fe57e6e6c4456c8ac_720w.jpg","美腿图片1.jpg");
TestThread2 T2=new TestThread2("https://pic2.zhimg.com/80/v2-365543141033d69e5c8bc933bdbc2745_720w.jpg","美腿图片2.jpg");
TestThread2 T3=new TestThread2("https://pic1.zhimg.com/80/v2-1937e5d15c49149c3ce2e908ce0d111c_720w.jpg","美腿图片3.jpg");
//4,调用start方法启动线程
T1.start();
T2.start();
T3.start();
}
}
//下载器
class WebDownloader{
//下载方法
public void Downloader(String UrlName,String filename)
{
try {
FileUtils.copyURLToFile(new URL(UrlName),new File(filename));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,d1方法出现问题");
}
}
}
备注:FileUtils.copyURLToFile(new URL(地址链接),new File(下载后保存的图片名));
使用方法:百度下载commons-io-2.8.0-bin.zip,解压后把commons-io-2.8.0.jar复制到项目的lib目录下(新建),对lib目录进行add as library操作。
方法二:自定义类实现Runnable接口
1,创建实现类 去实现Runnable接口
2,在实现类中重写run()方法,编写线程执行体
3,创建runnable接口的实现类对象,并传入Thread构造器,创建Thread类的对象
4,通过Thread类的对象调用start()开启线程
package L6;
//1,创建实现类 去实现Runnable接口
public class TestThread3 implements Runnable{
public void run(){
//2,在实现类中重写run()方法,编写线程执行体
for (int i = 0; i < 500; i++) {
System.out.println("我在看代码--"+i);
}
}
public static void main(String[] args) {
//3,创建runnable接口的实现类对象
TestThread3 T3=new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
//再把runnable接口的实现类对象T3丢入
//Thread构造器的参数需要一个目标runnable实现类对象
//Thread p=new Thread(T3);
//p.start(); //此两行可以直接总结为以下一行
new Thread(T3).start(); //将runnable接口的实现类对象,丢入Thread构造器,并用start()开启线程
for (int i = 0; i < 500; i++) {
System.out.println("我在学习多线程--"+i);
}
}
}
注:推荐使用方法二:Runnable接口的实现使得:“将任务和线程完全分离”,即:使得可以创建一个任务由多个线程来执行
方法三:匿名类创建线程
package L6;
//匿名内部类创建线程有两种方式
public class testThread5 {
public static void main(String[] args) {
方式1:相当于继承了Thread类,作为子类重写run()实现
new Thread(){
@Override
public void run() {
System.out.println("匿名内部类创建线程方式1...");
}
}.start();
//方式2:实现Runnable,Runnable作为匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类创建线程方式2...");
}
}){
}.start();
}
}
多个线程操作一个对象
示例代码:
package L6;
//多个线程操作同一个对象
//买火车票的例子
public class TestThread4 implements Runnable{
//票数
private int TicketNums=10;
@Override
//重写run方法
public void run() {
while (true){
if(TicketNums==0){
break;
}
try {
//做一个延迟
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName()返回对当前正在执行的线程对象的名字
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+(TicketNums--)+"张票");
}
}
public static void main(String[] args) {
TestThread4 ticket=new TestThread4();
new Thread(ticket,"小红").start();
new Thread(ticket,"小明").start();
new Thread(ticket,"老李").start();
}
}
//会发现问题,多个线程操作一个对象会造成数据紊乱
静态代理
-
概念:
代理模式:使用一个代理对象将对象包装起来,然后用该代理对象来取代该对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时调用原始对象的方法
静态代理:要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。
public class StaticProxy { public static void main(String[] args) { you Y1=new you(); WeddingCompany W1=new WeddingCompany(Y1); W1.HappyMarry(); } } //定义结婚接口 interface MARRY{ //声明结婚方法 void HappyMarry(); } //真实角色,自己结婚。被代理类 class you implements MARRY{ @Override //实现MARRY接口中的HappyMarry() public void HappyMarry() { System.out.println("GQ要结婚了"); } } //代理角色,帮助你结婚。代理类 class WeddingCompany implements MARRY{ //新建对象target来接接收传入的对象 private MARRY target; //target代表了所有MARRY接口的实现类 所实例化的对象。比如Y1 public WeddingCompany(MARRY target){ this.target=target; //接收了传入对象,此时this.target就等于传入对象 } //实现MARRY接口中的HappyMarry() @Override public void HappyMarry() { before(); this.target.HappyMarry();//真实对象,此处所调用的方法也是传入对象所属类的方法 after(); } private void before(){ System.out.println("结婚之前"); } private void after(){ System.out.println("结婚之后"); } }
Lambda表达式
-
函数式接口:
1,任何接口,如果只包含唯一一个抽象方法,那么它就是个函数式接口。
2,对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
-
Lambda表达式语法:
左侧 : Lambda 表达式的参数列表
右侧 : Lambda 表达式中所需执行的功能, 即 Lambda 体
//示例代码 1,() -> System.out.println("Hello Lambda!"); 2,(x) -> System.out.println(x) 3,x -> System.out.println(x) //若只有一个参数,小括号可以省略不写 4,//有两个以上的参数,有返回值,并且 Lambda 体中有多条语句 Comparator <Integer> com = (x, y) -> { System.out.println("函数式接口"); return Integer.compare(x, y);};
public class LambdaTest2 { public static void main(String[] args) { //lambda简化 Iliove L1; L1=(int a)->{System.out.println("i love lambda->"+a);}; L1.lambda(250); //简化:去掉参数类型、小括号、和花括号 L1=a->System.out.println("i love lambda->"+a); L1.lambda(520); } } //定义一个函数式接口Ilove interface Iliove{ void lambda(int a); }
线程停止
- 不推荐使用jdk提供的stop()、destroy()方法
- 推荐使用一个标志变量。让线程自己停下来
线程休眠-sleep
- 每个对象都有一个锁,sleep不会释放锁
- sleep存在异常interruptedException
- sleep时间达到后,线程进入就绪状态
public class testSleep {
//给这个方法加上static,就能在main方法中直接调用
public static void tendown() throws InterruptedException {
int num=10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num==0){
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
tendown();
}
}
线程礼让-yield
-
礼让线程,能让当前正在执行的线程暂停,但并不阻塞
-
将线程从运行状态转化为就绪状态
-
让cpu重新调度,礼让不一定成功。看cpu心情。即礼让以后、几个线程重新竞争,随机进入
public class TestYield { public static void main(String[] args) { //多个线程操作一个对象 myyield M1=new myyield(); new Thread(M1,"一号线程").start(); new Thread(M1,"二号线程").start(); } } class myyield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield(); System.out.println(Thread.currentThread().getName()+"线程停止执行"); } }
线程强制执行-join
-
可以理解为插队
package L6; //join方法即插队 public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("vip插队-->"+i); } } public static void main(String[] args) throws InterruptedException { TestJoin T1=new TestJoin(); //此处不合并写成new Thread(T1).start(); 因为需要对线程P做操作 Thread P=new Thread(T1); P.start(); //主线程 for (int i = 0; i < 500; i++) { if (i==200){ //阻塞主线程,强制执行线程P P.join(); } System.out.println("main运行"+i); } } }
线程状态观测
线程五大状态:
-
新建状态(New)
-
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
-
阻塞(BLOCKED):表示线程阻塞于锁
-
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
-
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
-
终止(TERMINATED):表示该线程已经执行完毕。
线程优先级
- 获取优先级:P.getPriority() (P是线程对象)
- 设置优先级权重:P.setPriority()
package L6;
public class TestPriority {
public static void main(String[] args) {
//主线程
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority M1=new MyPriority();
//其他线程
//多个线程操作一个实现类对象
Thread T1=new Thread(M1);
Thread T2=new Thread(M1);
Thread T3=new Thread(M1);
Thread T4=new Thread(M1);
//先设置线程优先级、再启动线程
T1.start();
T2.setPriority(1);
T2.start();
T3.setPriority(5);
T3.start();
T4.setPriority(Thread.MAX_PRIORITY);
T4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程
-
线程分为用户线程和守护线程
-
虚拟机必须保证用户线程执行完毕,而不用等待守护线程执行完毕。用户线程结束后,虚拟机会停掉守护线程
-
守护线程如:后台记录操作日志、监控内存、垃圾回收等等
package L6; import jdk.internal.org.objectweb.asm.tree.MultiANewArrayInsnNode; public class TestDaemon { public static void main(String[] args) { God G1=new God(); You Y1=new You(); Thread T1=new Thread(G1); T1.setDaemon(true); //false表示是用户线程,true表示是守护线程 //守护线程启动 T1.start(); //用户线程启动 new Thread(Y1).start(); } } class God implements Runnable{ @Override public void run() { while (true){ System.out.println("上帝与你同在!"); } } } class You implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("你的一生都快乐的活着!"); } System.out.println("挂了!"); } }
解决线程安全1:同步代码块
-
当多个线程操作一个对象时,会带来访问冲突和对象值混乱等问题。为了解决这个问题,加入了锁机制synchronized,当一个线程获得对象的排他锁时,会独占资源。其他线程必须等待。
-
synchronized机制:包括synchronized方法和synchronized块
-
synchronized块:(即同步代码块)
synchronized(同步监视器){ //需要被同步的代码 } 说明:1,需要被同步的的代码,就是操作共享数据的代码 2,共享数据:多个线程共同操作的变量 3,同步监视器,俗称:锁。任何一个类的对象,都可以作为锁 要求:多个线程必须共用同一把锁
//通过同步代码块解决 实现runnable接口的线程安全问题 public class windowTest1 { public static void main(String[] args) { window1 W1=new window1(); //三个线程操作一个对象W1 Thread T1=new Thread(W1); Thread T2=new Thread(W1); Thread T3=new Thread(W1); T1.setName("窗口1"); T2.setName("窗口2"); T3.setName("窗口3"); T1.start(); T2.start(); T3.start(); } } class window1 implements Runnable{ private int ticket=100; //主函数只创建了一个对象,所以obj只被创建了一次,所有线程共用这一个锁 private Object obj=new Object(); @Override public void run() { synchronized (obj){ //以下都是需要被同步的代码 while (true){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+(ticket--)); }else { break; } } } } } //通过同步代码块解决 继承Thread类的线程安全问题 public class windowTest2 { public static void main(String[] args) { //创建三个线程 window2 W1=new window2(); window2 W2=new window2(); window2 W3=new window2(); W1.setName("窗口1"); W2.setName("窗口2"); W3.setName("窗口3"); W1.start(); W2.start(); W3.start(); } } class window2 extends Thread{ private int ticket=100; //加上static以后,不论new几个对象,所有线程都共用一个锁 private static Object obj=new Object(); @Override public void run() { synchronized (obj){ //以下都是需要被同步的代码 while (true){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+(ticket--)); }else { break; } } } } }
解决线程安全2:同步方法
-
synchronized方法:即同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步
//通过同步方法解决 实现runnable接口的线程安全问题
public class windowTest3 {
public static void main(String[] args) {
window3 W1=new window3();
//三个线程操作一个对象W1
Thread T1=new Thread(W1);
Thread T2=new Thread(W1);
Thread T3=new Thread(W1);
T1.setName("窗口1");
T2.setName("窗口2");
T3.setName("窗口3");
T1.start();
T2.start();
T3.start();
}
}
class window3 implements Runnable{
private int ticket=100;
@Override
//不可在此处加synchronized,因为while (true){}被包入同步区域的话,第一个进来的线程会循环到结束为止
public void run() {
while (true){
show();
}
}
synchronized void show(){ //此时同步监视器是this
if(ticket>0){
//线程休眠等于是个等待区,让几个线程都到达等待区,再进入下一步对共享数据的操作。这样可以放大线程中的问题所在
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+(ticket--));
}
}
}
//通过同步方法解决 继承thread的线程安全问题
public class windowTest4 {
public static void main(String[] args) {
//创建三个线程
window4 W1=new window4();
window4 W2=new window4();
window4 W3=new window4();
W1.setName("窗口1");
W2.setName("窗口2");
W3.setName("窗口3");
W1.start();
W2.start();
W3.start();
}
}
class window4 extends Thread{
private static int ticket=100;
@Override
public void run() {
//以下都是需要被同步的代码
while (true){
show();
}
}
static synchronized void show(){ //同步监视器是window4.class
if(ticket>0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + (ticket--));
}
}
}
同步方法总结:
- 同步方法依然涉及同步监视器,只是不需要显示声明
- 非静态的同步方法,同步监视器是:this 即该类的对象
- 静态的同步方法,同步监视器是:当前类本身 类.class
- 在继承thread创建多线程的方式中,慎用this作为同步监视器。因为对象可能不唯一
死锁
概念:
- 不同的线程分别占用对方所需要的同步资源不放弃,都在等待对方放弃。就形成了线程的死锁
- 出现死锁以后,不会出现异常和提示,只是所有线程都处于阻塞状态,无法继续
//死锁示例1
public class DeadLock01 {
public static void main(String[] args) {
StringBuffer S1=new StringBuffer();
StringBuffer S2=new StringBuffer();
new Thread(){
//此线程需要两把锁,先S1后S2
@Override
public void run() {
synchronized (S1){
S1.append("a");
S2.append("b");
//休眠,提升死锁发生概率
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (S2){
S1.append("c");
S2.append("d");
System.out.println(S1);
System.out.println(S2);
}
}
}
}.start();
new Thread(new Runnable() {
//此线程需要两把锁,先S2后S1
@Override
public void run() {
synchronized (S2){
S1.append("E");
S2.append("F");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (S1){
S1.append("G");
S2.append("H");
System.out.println(S1);
System.out.println(S2);
}
}
}
}).start();
}
}
注:此代码出现死锁的情况。
当一个线程占据S1锁,需要进入S2锁时。另一个线程占据S2锁,需要进入S1锁
二者陷入对峙,形成死锁
//死锁示例2
package L6.Lock;
class A{
public synchronized void foo(B b){ //监视器:A类的对象,此时为a
System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了A实例的foo方法");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B实例的last方法");
b.last();
}
public synchronized void last(){ //监视器:A类的对象,此时为a
System.out.println("进入了A类的last方法内部");
}
}
class B{
public synchronized void bar(A a){ //同步监视器:b
System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了B实例的bar方法");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A实例的last方法");
a.last();
}
public synchronized void last(){ //同步监视器:b
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock02 implements Runnable{
A a=new A();
B b=new B();
public void init(){
Thread.currentThread().setName("主线程");
//调用a对象的foo方法
a.foo(b);
System.out.println("进入主线程之后");
}
@Override
public void run() {
Thread.currentThread().setName("副线程");
//调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
//分线程,启动run方法
DeadLock02 D=new DeadLock02();
new Thread(D).start();
//主线程
D.init();
}
}
分析:
主线程,需要先握住锁a,进入a的同步方法foo。然后握住锁b,进入b的同步方法b.last()
分线程,需要先握住锁b,进入b的同步方法bar。然后握住锁a,进入a的同步方法a.last()
这样两个线程陷入对峙,互相阻塞,形成死锁
解决线程安全3:Lock锁
Lock与synchronized的异同?
-
相同:二者都可以解决线程的安全问题
-
不同:synchronized在执行完相应的同步代码以后,自动的释放锁。属于隐式锁
lock需要手动启动和手动结束。属于显式锁
package L6.Lock;
import java.util.concurrent.locks.ReentrantLock;
class window implements Runnable{
//1,实例化一个ReentrantLock类的对象
private ReentrantLock lock01=new ReentrantLock();
private int ticket=100;
@Override
public void run() {
while (true){
try {
//2,调用锁定方法lock。以下一块代码就变成了单线程
lock01.lock();
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + (ticket--));
}else {
break;
}
} finally {
//3,调用解锁方法unlock
lock01.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
window W1=new window();
Thread T1=new Thread(W1);
Thread T2=new Thread(W1);
Thread T3=new Thread(W1);
T1.setName("窗口1");
T2.setName("窗口2");
T3.setName("窗口3");
T1.start();
T2.start();
T3.start();
}
}
同步机制课后练习:存钱
要求:
- 两个储户分别向一个银行账户存钱。每次存一千,存三次
- 每次存完打印账户余额
package L6.Lock;
public class accountTest {
public static void main(String[] args) {
//定义一个account类的对象,传入两个customer类的实现对象的构造器中。此时两个储户线程共用一个账户
account acc=new account(0);
customer c1=new customer(acc);
customer c2=new customer(acc);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
class account{
//1,account类中包含balance属性,可通过构造器赋值
//2,account类中包含deposit方法
private double balance;
public account(double balance) {
this.balance = balance;
}
public synchronized void deposit(double a){
if(a>0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance += a;
System.out.println(Thread.currentThread().getName()+"存款成功,当前余额为:" + balance);
}
}
}
class customer extends Thread{
//在customer类中定义一个account的属性,通过customer构造器传入。
private account acc;
public customer(account acc){
//在构造器中实例化属性
this.acc=acc;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
acc.deposit(1000);
}
}
}
线程通信,交替执行
-
wait():阻塞当前线程并释放当前的锁
-
notify():唤醒一个wait中的线程。如果有多个线程被wait,就唤醒优先级高的那个
-
notifyAll():唤醒所有wait中的线程
-
注:这三个方法必须使用在同步方法或者同步代码块中
-
实现交替的原理:
当线程1拿到锁,然后完成共享数据操作以后,执行wait(),陷入阻塞,并解开线程1的锁
线程1的锁解开以后,等待的线程2便可以进入,线程2拿到锁,执行了notify,唤醒了线程1,线程1循环至外部继续等待
线程2在内部执行一样的操作,循环往复,交替执行
//线程通信示例1,实现两个线程交替打印1-100
public class communication {
public static void main(String[] args) {
number N1 = new number();
Thread T2 = new Thread(N1);
Thread T1= new Thread(N1);
T1.setName("线程1");
T2.setName("线程2");
T1.start();
T2.start();
}
}
class number implements Runnable{
private int num=1;
@Override
public void run() {
while (true){
synchronized (this) {
//唤醒wait的线程
notify();
if (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num++);
//wait:阻塞当前线程并且释放当前锁
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
生产者消费者问题:
- 生产者(productor)将产品交给店员(clerk),消费者(customer)从店员处取走产品
- 店员一次只能取走固定数量的产品(定为20),如果生产者试图生产更多产品,店员会叫停生产者。
- 如果店中产品小于20,店员会让生产者继续生产。如果店中没有产品,店员会让消费者等待至有产品
//线程通信示例2,生产者消费者问题