目录
一,进程和线程
进程:进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。
线程:线程是比进程更小的执行单位。进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念(最小执行单位)。
主线程:每个java程序都有一个默认的主线程,当JVM加载代码发现main方法之后,就会立即启动一个线程,这个线程称为主线程 ,主线程是产生其他线程的线程。
二,创建线程的两种方式
1.继承Thread类
(1)继承Thread类,重写run方法;
(2)实例化Thread类的对象,调用start方法,启动线程;
(3)此时主线程会创建一个子线程,主线程和子线程是同时进行的,主线程结束,不一定导致整个应用程序的结束;
注意:直接启动run方法不会启动线程,运行的还是主线程,真正实现多线程的是start中的start0方法,而不是run方法;
package Text_Thread;
public class Demo {
public static void main(String[] args) throws InterruptedException{
Test test = new Test();
test.start(); //start启动线程,会自动调用重写的run方法
//这里,主线程会创建一个子线程,主线程和子线程是同时进行的
//主线程结束,不一定会导致整个应用程序结束
//test.run() 直接启动run方法不会启动线程,运行的还是main线程
//真正实现多线程的是start中的start0方法,而不是run方法xiancheng
for(int i = 1; i <= 10; i++){
System.out.println("主线程:" + i + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class Test extends Thread{
@Override
public void run() { //重写run方法,写上自己的业务逻辑
int times = 1;
while(true){ //每隔一秒,输出一次启动
System.out.println("子线程:启动" + (times++) + Thread.currentThread().getName());
try {
sleep(1000); //休眠一秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times == 10)break;
}
}
}
2.实现Runnable接口
因为Java的单继承机制,若一个类已经继承了一个父类,就无法继承Thread类,此时可以通过实现Runnable接口的方法来创建线程。
(1)创建一个Test类实现Runnable接口,重写run方法;
(2)实例化一个Test类对象;
(3)用Test类对象实例化一个Thread类对象thread;
(4)调用thread的start方法,启动线程,因为Runnable中没有start方法,只能通过实例化一个Thread类来调用start方法,实际上Thread类也实现了Runnable接口;
package Text_Thread;
//因为Java的单继承特性,若一个类已经继承了一个父类,就无法继承Thread,这是可以通过实现Runnable来创建线程
public class Demo_Runnable {
public static void main(String[] args) {
Test_Runnable tr = new Test_Runnable();
Thread thread = new Thread(tr);
thread.start(); //Runnable没有start方法,只能通过实例化一个Thread类来调用start方法
}
}
class Test_Runnable implements Runnable{
@Override
public void run() {
int times = 0;
while(true){
System.out.println("Hello!" + (++times) +" " +Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times == 10)break;
}
}
}
3.模拟Runnable接口
Runnable接口中应该有以下几个内容:
(1)构造器,可以接受实现Runnable接口的对象;
(2)start方法和start0方法;
(3)run方法;
模拟代码如下,可以看到真正调用run方法的是start0方法,所以说真正实现多线程的是start中的start0方法,而不是run方法;
package Text_Thread;
import javax.sound.sampled.Port;
public class Text_Proxy {
public static void main(String[] args) {
Test_Runnable tr = new Test_Runnable();
Proxy proxy = new Proxy(tr);
proxy.start();
}
}
//模拟极简的Runnable运行过程, 代理模式
class Proxy implements Runnable{
private Runnable target = null;
@Override
public void run() {
if(target != null)
{
target.run();
}
}
public Proxy(Runnable target){
this.target = target;
}
public void start(){
start0();
}
public void start0(){
run();
}
}
三,多个子线程
main线程和子线程已经算是启动了多线程,那么如何开启多个子线程?
1.继承Thread类,实例化多个对象
继承Thread类后,实例化多个对象,并且调用它们的start方法,就可以开启多个子线程。不过通过这种方法,多个线程所对应的对象是不同的。
package Test;
public class Test {
public static void main(String[] args) {
Thread0 thread01 = new Thread0();
Thread0 thread02 = new Thread0();
thread01.start();
thread02.start();
}
}
class Thread0 extends Thread{
private int times = 100;
@Override
public void run() {
while(true)
{
if(times <= 0)
{
System.out.println("剩余:" + times);
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "执行第" + times-- + "个子任务" );
}
}
}
程序的输出如下,可以看到两个线程没有在共同执行一个任务,因为两个线程所对应的资源是不同的。
2.实现Runnable接口,实例化多个Thread类对象
我们在通过Runnable接口实现多线程时,需要创建Thread类对象,并通过构造器传入实现Runnable的对象,那么在这里,我们可以实例化多个Thread类对象,并传入相同的对象,这样不同线程所对应的对象就是相同的。也就是说:实现Runnable接口方式更加符合多个线程共享一个资源的情况,并且避免了单继承机制。
package Test;
public class Test {
public static void main(String[] args) {
Thread01 thread01 = new Thread01();
Thread thread = new Thread(thread01);
Thread thread1 = new Thread(thread01);
thread.start();
thread1.start();
}
}
class Thread01 implements Runnable{ //实现Runnable接口方式更加符合多个线程共享有一个资源的情况,并且避免了单继承机制
private int times = 100;
@Override
public void run() {
while(true)
{
if(times <= 0)
{
System.out.println("剩余:" + times);
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "执行第" + times-- + "个子任务" );
}
}
}
程序的输出如下,可以看到两个线程确实在共同执行一个任务。
四,线程同步 互斥锁
创建多线程执行任务,有时可能会产生冲突,例如上面的代码执行时,最后剩余了-1个子任务,结果显然是错误的,这是因为在剩余1个子任务时,Thread-0和Thread-1又都执行了一次。为了避免这种情况,我们希望在一个线程执行一个任务时,别的线程不会来执行这个任务,这时需要用到synchronized同步。
1.同步代码块
Java中每个对象都对应一个称为“互斥锁”的标记,关键字synchronized 与对象互斥锁联合起来使用保证对象在任意时刻只能由一个线程访问,也就是这段代码被上锁了,这里的对象可以是任意对象,可以是this当前对象,表示每个对象对应一把锁,也可以是类对应的字节码文件类名.class,表示该类所有对象对应同一把锁。
synchronized(/*对象*/){
//可能发生冲突的代码
}
给run方法中的代码上锁,实现同步。注意想要实现同步的话,所有线程所对应的锁应该是同一把,即synchronized()中的对象应为同一个。
package Text_Thread;
public class Lock {
public static void main(String[] args) {
TestSynchronous testSynchronous = new TestSynchronous();
Thread thread = new Thread(testSynchronous);
Thread thread1 = new Thread(testSynchronous);
Thread thread2 = new Thread(testSynchronous);
thread.start();
thread1.start();
thread2.start();
}
}
class TestLock implements Runnable{
private int doTimes = 100;
private boolean flag = true;
@Override
public void run() {
while(flag){
//doit();
synchronized (/*TestLock.class*/ this){ //同步代码块
if(doTimes <= 0)
{
flag = false;
System.out.println("剩余" + doTimes + "个子任务");
return;
}
System.out.println("线程:" + Thread.currentThread().getName() + " 执行第 " + doTimes-- + " 个子任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
运行成功
2.同步方法
用synchronized修饰方法,这个方法就成为了同步方法,表示每个时刻最多只有一个线程执行这个方法。非静态同步方法的锁默认是当前类的对象,即this,静态同步方法的锁默认是当前类的字节码文件,即 类名.class。
package Text_Thread;
public class Synchronous {
public static void main(String[] args) {
TestSynchronous testSynchronous = new TestSynchronous();
Thread thread = new Thread(testSynchronous);
Thread thread1 = new Thread(testSynchronous);
Thread thread2 = new Thread(testSynchronous);
thread.start();
thread1.start();
thread2.start();
}
}
class TestSynchronous implements Runnable{
private int doTimes = 100;
private boolean flag = true;
public synchronized void doit(){ //Synchronous 同步方法,每个时刻最多只有一个线程执行这个方法
if(doTimes <= 0)
{
flag = false;
System.out.println("剩余" + doTimes + "个子任务");
return;
}
System.out.println("线程:" + Thread.currentThread().getName() + " 执行第 " + doTimes-- + " 个子任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
while(flag){
doit();
}
}
}
运行结果如下,没有发生冲突。
五,死锁
假设有这样一种情况,线程A想要完成需要用到线程B提供的资源,但线程B完成又需要线程A先完成,两个线程的执行顺序形成了一个环,从而导致死锁问题。代码示例如下:
package Text_Thread;
//模拟死锁
public class DeadLock {
public static void main(String[] args) {
TestDeadLock A = new TestDeadLock(true);
Thread Thread_A = new Thread(A);
TestDeadLock B = new TestDeadLock(false);
Thread Thread_B = new Thread(B);
Thread_A.setName("线程A");
Thread_B.setName("线程B");
Thread_A.start();
Thread_B.start();
}
}
class TestDeadLock implements Runnable{
static Object o1 = new Object(); //static 表示该类所有对象共享o1, o2对象
static Object o2 = new Object();
boolean flag;
@Override
public void run() {
if(flag)
{
synchronized (o1){ //线程A需要线程B完成,线程B又需要线程A完成,从而导致死锁
System.out.println(Thread.currentThread().getName() + "进入1");
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "进入2");
}
}
}
else{
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "进入2");
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "进入1");
}
}
}
}
public TestDeadLock(boolean flag)
{
this.flag = flag;
}
}
运行后发现程序卡住,无法运行,产生了死锁。
六,线程常用方法
1.线程插队 join
现在有两个线程join1和join2,如果想要join1线程在join2线程执行完之后才执行,可以用线程插队join,在join1线程中调用join2.join(),join2就会插在join1之前执行,join2执行完后,join1才会执行。这里用main线程举例:
package Text_Thread.Method;
public class Cut {
public static void main(String[] args) throws InterruptedException{
ThreadCut threadCut = new ThreadCut();
Thread thread = new Thread(threadCut);
thread.start();
for(int times = 1; times <= 20; times++){
System.out.println("main线程:" + times);
Thread.sleep(1000);
if(times == 5)
{
System.out.println("注意:子线程开始插队...");
//thread.start();
thread.join(); //插队,join方法可以让子线程插队,即main线程停止,子线程执行完后,main线程才恢复执行
}
}
}
}
class ThreadCut implements Runnable{
@Override
public void run() {
int times = 0;
while(true)
{
System.out.println("子线程:" + ++times);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times == 20)break;
}
}
}
2.守护线程 Daemon
假如有两个线程A,B,将B设为A的守护进程,那么A线程结束后,B线程会跟着结束。
package Text_Thread.Method;
import java.util.DuplicateFormatFlagsException;
public class Daemon {
public static void main(String[] args) throws InterruptedException {
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true); //将子线程设为守护线程,main线程结束后,子线程会跟着结束
thread.start();
for(int times = 1; times <= 10; times++)
{
System.out.println("main线程:" + times);
Thread.sleep(1000);
}
}
}
class DaemonThread implements Runnable{
@Override
public void run() {
int times = 0;
while(true)
{
System.out.println("子线程:" + ++times);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times == 20)break;
}
}
}
3.线程中断 线程唤醒 interrupt
调用interrupt方法,可以唤醒正在休眠的线程。
package Text_Thread.Method;
//线程中断
public class Interrupt {
public static void main(String[] args) throws InterruptedException {
ThreadInterrupt ti = new ThreadInterrupt();
ti.start();
for(int i = 1; i <= 5; i++)
{
Thread.sleep(1000);
System.out.println("子线程休眠即将中断:" + i);
}
ti.interrupt(); //子线程休眠中断
}
}
class ThreadInterrupt extends Thread{
//private int times = 0;
private boolean flag = true;
@Override
public void run() {
while(flag){
for(int times = 1; times <= 100; times++)System.out.println("子线程运行:" + times + " 次");
try {
System.out.println("子线程开始休眠...");
Thread.sleep(1000 * 20); //线程休眠20秒
} catch (InterruptedException e) {
System.out.println("线程休眠中断...");
}
}
}
}