线程
概述
- 线程与进程:进程是一个应用程序执行一次的过程,windows系统下可以查看应用程序进程,在一次进程执行中可以分为多个线程的执行。
- 多进程使用情况:i.需要同时执行多个任务。ii.实现需要等待的任务。iii.需要后台运行的程序。
- 创建多线程的方式:i.继承Thread类。ii.实现Runnable接口。两种方式都需要重写run方法。
- 多线程同时运行时会抢占cpu资源,输出时可能导致结果混乱。
线程的两种实现方法
- 代码实现
package com.leeyu.testthread;
/**
* 实现两种方式定义线程类
* @author lijie
*
*/
public class TestThread1 {
public static void main(String[] args) {
//继承thread类的线程thread1
Thread thread1=new Thread01();
//实现Runnable接口的线程thread2(注意初始化的区别)
Thread thread2=new Thread(new Thread02());
//启动线程
thread1.start();
thread2.start();
//使用main方法打印0-99
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class Thread01 extends Thread{
@Override
public void run() {
//打印0-99
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
super.run();
}
}
class Thread02 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}// TODO Auto-generated method stub
}
}
结果----------------------
main:0
main:1 main:2 main:3 main:4 main:5
main:6 main:7 main:8 main:9 main:10
main:11 main:12 main:13 main:14 main:15
main:16 main:17 main:18 main:19 main:20
main:21 main:22 main:23 main:24 main:25
main:26 main:27 main:28 main:29 main:30
main:31 main:32 main:33 main:34 main:35
main:36 main:37 main:38 main:39 Thread-1:0 Thread-0:0
main:40
main:41 Thread-1:1
Thread-0:1 Thread-0:2 Thread-0:3 Thread-0:4 Thread-0:5
Thread-0:6 Thread-0:7 Thread-0:8 Thread-0:9 Thread-1:2 Thread-1:3 Thread-1:4 main:42 main:43 Thread-1:5
Thread-1:6 Thread-1:7 Thread-1:8 Thread-1:9 Thread-1:10
Thread-1:11 Thread-1:12 Thread-1:13 Thread-1:14 Thread-0:10
Thread-1:15
main:44 Thread-1:16 Thread-1:17 Thread-0:11 Thread-0:12 Thread-1:18 main:45 Thread-1:19
main:46 main:47 main:48 Thread-0:13 Thread-0:14 Thread-0:15
main:49 main:50 Thread-1:20
Thread-0:16 Thread-0:17 main:51 Thread-1:21 main:52 Thread-0:18 main:53 Thread-1:22 Thread-1:23 main:54 main:55
Thread-0:19 Thread-0:20
Thread-0:21 Thread-0:22 main:56 main:57 main:58 main:59 main:60
main:61 main:62 main:63 Thread-1:24 Thread-1:25 main:64 main:65
main:66 main:67 main:68 main:69 main:70
main:71 main:72 main:73 main:74 main:75
main:76 main:77 main:78 main:79 main:80 Thread-0:23
Thread-1:26 Thread-1:27 Thread-1:28 Thread-1:29 Thread-1:30
Thread-1:31 Thread-1:32 main:81 main:82 main:83 Thread-0:24 Thread-0:25
Thread-0:26 Thread-0:27 Thread-0:28 Thread-0:29 Thread-0:30
Thread-0:31 Thread-0:32 Thread-0:33 main:84 main:85
main:86 main:87 main:88 main:89 main:90
main:91 Thread-1:33 main:92 main:93 main:94 main:95
main:96 main:97 main:98 main:99 Thread-0:34 Thread-0:35 Thread-1:34
Thread-0:36 Thread-0:37 Thread-0:38 Thread-0:39 Thread-0:40
Thread-0:41 Thread-0:42 Thread-0:43 Thread-0:44 Thread-0:45
Thread-0:46 Thread-0:47 Thread-0:48 Thread-0:49 Thread-0:50
Thread-0:51 Thread-0:52 Thread-0:53 Thread-0:54 Thread-0:55
Thread-0:56 Thread-0:57 Thread-0:58 Thread-0:59 Thread-0:60
Thread-0:61 Thread-0:62 Thread-0:63 Thread-0:64 Thread-0:65
Thread-0:66 Thread-0:67 Thread-0:68 Thread-0:69 Thread-0:70
Thread-0:71 Thread-0:72 Thread-0:73 Thread-0:74 Thread-0:75
Thread-0:76 Thread-0:77 Thread-0:78 Thread-0:79 Thread-0:80
Thread-0:81 Thread-0:82 Thread-0:83 Thread-0:84 Thread-0:85
Thread-0:86 Thread-0:87 Thread-0:88 Thread-0:89 Thread-0:90
Thread-0:91 Thread-0:92 Thread-0:93 Thread-0:94 Thread-0:95
Thread-0:96 Thread-0:97 Thread-0:98 Thread-0:99 Thread-1:35
Thread-1:36 Thread-1:37 Thread-1:38 Thread-1:39 Thread-1:40
Thread-1:41 Thread-1:42 Thread-1:43 Thread-1:44 Thread-1:45
Thread-1:46 Thread-1:47 Thread-1:48 Thread-1:49 Thread-1:50
Thread-1:51 Thread-1:52 Thread-1:53 Thread-1:54 Thread-1:55
Thread-1:56 Thread-1:57 Thread-1:58 Thread-1:59 Thread-1:60
Thread-1:61 Thread-1:62 Thread-1:63 Thread-1:64 Thread-1:65
Thread-1:66 Thread-1:67 Thread-1:68 Thread-1:69 Thread-1:70
Thread-1:71 Thread-1:72 Thread-1:73 Thread-1:74 Thread-1:75
Thread-1:76 Thread-1:77 Thread-1:78 Thread-1:79 Thread-1:80
Thread-1:81 Thread-1:82 Thread-1:83 Thread-1:84 Thread-1:85
Thread-1:86 Thread-1:87 Thread-1:88 Thread-1:89 Thread-1:90
Thread-1:91 Thread-1:92 Thread-1:93 Thread-1:94 Thread-1:95
Thread-1:96 Thread-1:97 Thread-1:98 Thread-1:99
- 可以看出,线程执行顺序和main方法中调用顺序并不一致。
线程常用方法
- 1.获取线程id和名称:Thread.currentThread().getId();和Thread.currentThread().getName();
- 2.优先级:获取:Thread.currentThread().getPriority();设置:
Thread.currentThread().setPriority(10); - 3.线程状态:NEW:表示新建但是没有调用的状态;
TERMINATED:表示运行结束之后的状态;
RUNNABLE:表示正在执行run方法的状态;
BLOCKED,WAITING,TIMED_WAITING:都表示阻塞状态。 - 4.sleep方法,可以让线程休眠指定时间
package com.leeyu.testthread;
import java.util.Date;
public class TestThread2 {
public static void main(String[] args) {
Thread thread1=new Thread(
new Runnable() {
public void run() {
//记录开始时间
Long start=System.currentTimeMillis();
try {
//设置线程睡眠
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//记录结束时间
Long end=System.currentTimeMillis();
Thread.currentThread().setName("thread1");
//打印1-10
for(int i=1;i<=10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
System.out.println(end-start);
}
});
thread1.start();
}
}
结果-------------------------------
thread1:1
thread1:2
thread1:3
thread1:4
thread1:5
thread1:6
thread1:7
thread1:8
thread1:9
thread1:10
3005
为什么打印出来的时间是3005(ms)不是3000整,可能是cpu利用了5ms的时间执行了其他程序的线程,之后才空余出来。
- 5.join方法,可以让外部线程等待当前线程执行完毕之后在执行。(需要注意,join方法需要插入到run方法内,实际的执行方法之前。)
package com.leeyu.testthread;
public class TestThread2 {
public static void main(String[] args) {
Thread thread1=new Thread(
new Runnable() {
public void run() {
//设置线程名为thread1
Thread.currentThread().setName("thread1");
//打印1-10
for(int i=1;i<=10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
});
Thread thread2=new Thread(
new Runnable(){
@Override
public void run() {
Thread.currentThread().setName("thread2");
//设置线程1先执行,线程二需要等待
try {
thread1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=1;i<=10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
});
thread2.start();
thread1.start();
}
}
结果------------
thread1:1
thread1:2
thread1:3
thread1:4
thread1:5
thread1:6
thread1:7
thread1:8
thread1:9
thread1:10
thread2:1
thread2:2
thread2:3
thread2:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9
thread2:10
多线程可能导致的问题
- 由于线程之间是共享数据的(这个特性的存在使得线程处理能力好),但是当不同线程尝试修改同一个数据时可能导致结果错误。
- 当对于数据的操作不是原子操作时使用多线程修改就可能出错.
package com.leeyu.testthread;
public class TestMutiThread {
private static int i=0;
//定义一个返回自定义线程实例的方法
private static Runnable getThread() {
return new Runnable(){
@Override
public void run() {
for(int num=0;num<5;num++) {
i++;
System.out.println("i=:"+i);
}
}
};
}
public static void main(String[] args) {
//定义三个线程
Thread[] mutiThread=new Thread[3];
//初始化三个线程为自定义线程
for(int n=0;n<mutiThread.length;n++) {
mutiThread[n]=new Thread(getThread());
}
//启动所有线程
for(Thread n:mutiThread) {
n.start();
}
}
}
结果----------------
i=:1
i=:2
i=:1
i=:4
i=:3
i=:7
i=:8
i=:9
i=:6
i=:10
i=:11
i=:5
i=:12
i=:13
i=:14
- 对于例中的代码来说,i++就是非原子操作。实现这个操作需要先获取i的值,然后计算i+1的值,最后赋给i,到此才算完成操作。这就导致当一个线程获取到i的值为1时另外一个线程也可以获取到值为1的i的值(当另外一个线程获取值得操作在第一个线程计算返回值之前完成的时候就会出现这种问题)。
- 还可能因为数据内存的可见性问题导致结果错误,当访问一个数据时,可能直接从寄存器或者缓存中,也可能从内存中获取。修改变量时,也可能先改到缓存中,之后再同步到内存中。这就导致数据不同步。
synchronized修饰符
- 修饰类的实例方法,静态方法,代码块。加入synchronized修饰符的代码块就变成了原子操作。sychronized修饰的是同一对象(不是同一个类)中的方法,对于多个变量可以同时访问不同变量的相同的synchronized方法。
- 修饰普通方法:得到的是对象锁。
package com.leeyu.testthread;
public class TestSynchronized {
public static void main(String[] args) {
Thread5 thread5 = new Thread5();
Thread thread1=new Thread(thread5);
Thread thread2=new Thread(thread5);
thread1.start();
thread2.start();
}
}
class Thread5 implements Runnable{
static int i=0;
//修饰普通方法
private synchronized void increase() {
i++;
System.out.println(Thread.currentThread().getName()+"-i:"+i);
}
@Override
public void run() {
for(int n=0;n<10;n++) {
increase();
}
}
}
结果----------------
Thread-0-i:1
Thread-0-i:2
Thread-0-i:3
Thread-0-i:4
Thread-0-i:5
Thread-0-i:6
Thread-0-i:7
Thread-0-i:8
Thread-0-i:9
Thread-0-i:10
Thread-1-i:11
Thread-1-i:12
Thread-1-i:13
Thread-1-i:14
Thread-1-i:15
Thread-1-i:16
Thread-1-i:17
Thread-1-i:18
Thread-1-i:19
Thread-1-i:20
- 修饰静态方法:注意和上方代码的区别(传入Thread构造器参数不同,下面代码是两个实例),但是由于synchronized修饰的是静态方法(属于类),所以得到的类锁。
package com.leeyu.testthread;
public class TestSynchronized {
public static void main(String[] args) {
Thread thread1=new Thread(new Thread5());
Thread thread2=new Thread(new Thread5());
thread1.start();
thread2.start();
}
}
class Thread5 implements Runnable{
static int i=0;
//修饰普通方法
private synchronized static void increase() {
i++;
System.out.println(Thread.currentThread().getName()+"-i:"+i);
}
@Override
public void run() {
for(int n=0;n<10;n++) {
increase();
}
}
}
结果---------------
Thread-0-i:1
Thread-0-i:2
Thread-0-i:3
Thread-0-i:4
Thread-0-i:5
Thread-0-i:6
Thread-0-i:7
Thread-0-i:8
Thread-0-i:9
Thread-0-i:10
Thread-1-i:11
Thread-1-i:12
Thread-1-i:13
Thread-1-i:14
Thread-1-i:15
Thread-1-i:16
Thread-1-i:17
Thread-1-i:18
Thread-1-i:19
Thread-1-i:20
- 修饰代码块:使用synchronized(){}来完成,会使不同线程之间争夺()中对象的锁。
package com.leeyu.testthread;
public class TestSynchronized2 {
public static void main(String[] args) {
Thread6 thread=new Thread6();
Thread thread1=new Thread(thread);
Thread thread2=new Thread(thread);
thread1.start();
thread2.start();
}
}
class Thread6 implements Runnable{
static int i;
@Override
public void run() {
for(int n=0;n<10;n++) {
//给调用run方法的线程对象上锁
synchronized(this){
i++;
System.out.println(Thread.currentThread().getName()+"I:"+i);
}
}
}
}
结果--------------------------------------
Thread-0I:1
Thread-0I:2
Thread-0I:3
Thread-0I:4
Thread-0I:5
Thread-0I:6
Thread-0I:7
Thread-0I:8
Thread-0I:9
Thread-0I:10
Thread-1I:11
Thread-1I:12
Thread-1I:13
Thread-1I:14
Thread-1I:15
Thread-1I:16
Thread-1I:17
Thread-1I:18
Thread-1I:19
Thread-1I:20
wait/notify控制线程
- wait与notify需要配合synchronized使用,wait会释放当前线程占用的锁,notify通知处于WAITING状态的对象继续执行,参与争夺对象锁。(并不会立即释放锁,会等待synchronized代码执行完毕)
- 使用wait和notify完成一个简单的生产消费模型。
package com.leeyu.testthread;
import java.util.ArrayList;
import java.util.List;
public class TestThreadWN {
public static void main(String[] args) {
//生产和消费的对象
List<String> list=new ArrayList();
//生产者线程对象
Thread producter=new Thread(new Thread7(list));
//消费者线程对象
Thread consumer=new Thread(new Thread8(list));
//启动线程
producter.start();
consumer.start();
}
}
class Thread7 implements Runnable{
private List<String> list;
public Thread7(List<String> list) {
this.list=list;
}
@Override
public void run() {
//对list上锁,保证数据修改同步性
synchronized(list) {
//进行三次生产过程
for(int i=0;i<3;i++) {
//当list元素个数不足10个开始生产
while(list.size()<10) {
list.add("str");
}
//生产完成之后通知并输出内容
list.notify();
System.out.println("producter notify");
System.out.println(list.size());
try {
System.out.println("producter wait");
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class Thread8 implements Runnable{
private List<String> list;
public Thread8(List<String> list) {
this.list=list;
}
@Override
public void run() {
synchronized(list) {
for(int n=0;n<3;n++) {
while(list.size()>0) {
//从后往前逐个消费
list.remove(list.size()-1);
}
//消费完成之后通知并输出内容
list.notify();
System.out.println("consumer notify");
System.out.println(list.size());
try {
System.out.println("consumer wait");
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
结果--------------------
producter notify
10
producter wait
consumer notify
0
consumer wait
producter notify
10
producter wait
consumer notify
0
consumer wait
producter notify
10
producter wait
consumer notify
0
consumer wait