一、创建多线程的方法一:
创建java.lang.Thread的子类,重写该类的run方法
基本用法例子:
public class ThreadTest1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 1、创建一个线程对象
Thread thr = new FirstThread();
// 2、调用线程对象的start()方法启动线程
thr.start();
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 100; i++){
System.out.println(threadName + ": " + i);
}
}
}
//继承Thread类
class FirstThread extends Thread{
/*
* 线程体在run()方法中
*/
public void run(){
// 显示当前线程名称
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 100; i++){
System.out.println(threadName + ": " + i);
}
}
}
//输出结果为:
main: 0
Thread-0: 0
Thread-0: 1
main: 1
main: 2
Thread-0: 2
main: 3
Thread-0: 3
Thread-0: 4
...
0~99 输出了两遍
思考一下如何去实现两个线程共享一个变量?
1. 思路一:是用static静态化一个变量
public class PrintNumber {
public static void main(String[] args){
Thread thread1 = new PrintThread("thread1");
Thread thread2 = new PrintThread("thread2");
thread1.start();
thread2.start();
}
}
class PrintThread extends Thread{
private static int i;
public PrintThread(String name){
super(name);
}
public void run(){
for (i = 0; i < 100; i++){
System.out.println(getName() + ": " + i );
}
}
}
2.思路二: 传递一个对象给一个进程,通过对象在进程中共享数据
public class PrintNumber {
public int i;
public static void main(String[] args){
PrintNumber pn1 = new PrintNumber();// 需要传递给进程的对象
Thread thread1 = new PrintThread("thread1", pn1);
Thread thread2 = new PrintThread("thread2", pn1);
thread1.start();
thread2.start();
}
}
class PrintThread extends Thread{
PrintNumber pn;
public PrintThread(String name, PrintNumber pn){
super(name);
this.pn = pn;
}
public void run(){
for (pn.i = 0; pn.i < 100; pn.i++){
System.out.println(getName() + ": " + pn.i );
}
}
}
二、创建多线程的方法二:
实现Runnable接口的方式:
1、创建实现Runnable接口的实现类:必须实现run()方法
2、创建第一步那个实现类的实例对象
3、利用Thread(Runnable target)构造器,创建Thread对象
4、调用Thread类的start方法调用线程
例子:
//实现MyRunnbale是Runnable的实现类
public class MyRunnable implements Runnable {
public static int i = 0;
@Override
// 实现run方法
public void run() {
for (; i < 100; i++){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
MyRunnable mr = new MyRunnable();
Thread th1 = new Thread(mr);
Thread th2 = new Thread(mr);
th1.start();
th2.start();
}
}
三、线程的生命周期
线程的生命周期指的是线程从启动,直到结束
可以调用Thread类的相关方法影响线程的运行状态
线程的运行状态有:
- 新建(new)
- 可执行(Runnable)
- 运行(Running)
- 阻塞(Blocking)
- 死亡(Dead)
新建状态
当新建了一个Thread对象时,该进程处于新建状态,没有启动,所以无法执行
可执行状态
其他线程调用了处于新建状态线程的start方法,该线程对象转换到“可执行状态”
线程拥有获得cpu的权利,处于等待调度阶段
运行状态
处于可执行状态的线程一旦获得cpu的控制权,就会转换到运行状态,
在执行状态下,线程状态占用cpu时间片段,执行run方法中的代码,处于执行状态下的线程可以调用yield方法,该方法用于主动让出cpu控制权。线程对象让出控制权后,回到可执行状态,重新等待调度;
阻塞状态
线程在执行状态下,由于某种条件影响,会被迫让出cpu控制权,进入“阻塞状态”
进入阻塞状态有三种情况:
- 调用sleep 方法:
Thread的sleep方法能让线程暂止休眠一段时间,单位是毫秒
实际例子:
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
// Thread的sleep方法能让线程暂止休眠一段时间,单位是毫秒
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
MyRunnable mr = new MyRunnable();
Thread th1 = new Thread(mr);
th1.start();
}
}
- 调用join方法
处于执行状态的调用了其他线程的join方法,将会挂起,进入阻塞状态
目标线程执行完毕后,才能解除阻塞,回到可执行状态;
public class ThreadJoin extends Thread{
public void run(){
for (int i = 0; i < 10; i++){
System.out.println(getName() + ": " + i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadJoin thread = new ThreadJoin();
thread.start();
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ": " + i);
if (i == 5){
try {
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
// 输出结果;
main: 0
Thread-0: 0
main: 1
main: 2
Thread-0: 1
main: 3
main: 4
Thread-0: 2
main: 5
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9
main: 6
main: 7
main: 8
main: 9
执行I/O操作
解除阻塞状态有三种情况:- 睡眠超时
- 调用join后等待其他线程执行完毕
- I/O操作完毕
- 调用堵塞线程的interrupter方法(线程睡眠时,调用该方法会抛出InterrupterException)
public class Interrupter extends Thread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Interrupter interrupter = new Interrupter();
interrupter.start();
interrupter.interrupt(); //解除线程的堵塞状态
}
public void run(){
for (int i = 0; i < 10; i++){
System.out.println(getName() + ": " + i);
if (i == 5){
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
执行结果:
Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
java.lang.InterruptedException: sleep interrupted //这是提示运行时异常
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9
at java.lang.Thread.sleep(Native Method)
at Interrupter.run(Interrupter.java:15)
死亡状态
处于执行状态的线程一旦从run()方法返回,无论是正常退出还是抛出异常,就会进入“死亡状态”;
已经死亡的线程不能重新运行,否则会抛出异常illegalThreadStateException
可以使用Thread类的isAlive方法判断线程是否活着;
四、线程的优先级
- Thread类提供了设置和获取当前优先级的方法:
setPriority();设置优先级不一定起作用,在不同的JVM、操作系统上,效果不同,操作系统不能保证设置了优先级的线程会优先执行或者获得更多的cpu时间
getPriority(); - Java设置了10个优先级,1~10;1最低,10最高,主线程默认优先级为5;
- 三个代表优先级的常量:MIN_PRIORITY、MAX_PRIORITY、NORM_PRIORITY,分别代表1、10、5
public class PriortityTest extends Thread{
public PriortityTest(String name) {
// TODO Auto-generated constructor stub
super(name);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
PriortityTest priThread0 = new PriortityTest("线程一"),
priThread1 = new PriortityTest("线程二1");
System.out.println(priThread0.getPriority());// 5
System.out.println(priThread1.getPriority());// 5
priThread0.setPriority(MIN_PRIORITY);//值为10,The maximum priority that a thread can have.
priThread1.setPriority(MAX_PRIORITY);
priThread0.start();
priThread1.start();
}
public void run(){
for (int i = 0; i < 10; i++){
System.out.println(getName() + ": " + i);
}
}
}
五、线程共享以及安全问题:
首先来看个例子:有五个苹果,两个线程同时拿苹果,每拿一个,打印谁拿了,剩下几个; 利用多线程,两个线程共享资源appleCount,这样在其中一个线程拿完一个苹果后,还没来的及打印,第二个线程已经将appCount--;所以会导致打印剩下的苹果时出现错位;
public class ShareApple implements Runnable{
private int appleCount = 5;
boolean getApple(){
if (appleCount > 0){
appleCount--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿走一个苹果," + "剩" + appleCount);
return true;
}
return false;
}
public void run(){
boolean flag = getApple();
while (flag){
flag = getApple();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ShareApple sa = new ShareApple();
Thread th1 = new Thread(sa);
Thread th2 = new Thread(sa);
th1.setName("shao");
th2.setName("jinghong");
th1.start();
th2.start();
}
}
//输出结果为:
shao拿走一个苹果,剩3
jinghong拿走一个苹果,剩3
shao拿走一个苹果,剩1
jinghong拿走一个苹果,剩1
shao拿走一个苹果,剩0
使用synchronized代码块解决线程安全问题
需要在synchronized代码块参照线程共同的对象:
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
synchornized(){
//...
}
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public synchronized void method(){
// ...
}
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
public synchronized static void method() {
// todo
}
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
synchronized关键字,确保共享资源只能在同一个时刻被一个线程访问,这种处理机制称为线程同步,或者线程互斥,基于“对象锁”的概念
public class ShareApple implements Runnable{
private int appleCount = 5;
boolean getApple(){
synchronized(this){
// this是一个共同的参照对象,这里指的是同一个ShareApple对象
if (appleCount > 0){
appleCount--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿走一个苹果," + "剩" + appleCount);
return true;
}
return false;
}
}
public void run(){
boolean flag = getApple();
while (flag){
flag = getApple();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ShareApple sa = new ShareApple();
Thread th1 = new Thread(sa);
Thread th2 = new Thread(sa);
th2.setName("shao");
th1.setName("jinghong");
th1.start();
th2.start();
}
}
线程通信
当一个线程在使用同步方法的某个参数时,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在线程中添加wait()方法;
相关方法:wait(), notify(), notifyAll();
这些方法必须在同步方法中调用。
例如:有如下情景,刘关张三个买票,售货员只有一张5元,张飞拿20元,刘备、关羽各拿五元,当张飞买票时,售货员会说:”没有足够多的零钱,需要等待后面的人买票后有零钱之后才能把票卖给你”;
public class Ticket implements Runnable{
private int fiveCount = 1, tenCount = 0, twentyCount = 0;
public synchronized void buy(){
String name = Thread.currentThread().getName();
if ("张飞".equals(name)){
if (fiveCount < 3){
try {
System.out.println("没有足够多的零钱,需要等待有零钱之后才能把票卖给张飞");
wait();
System.out.println("卖给张飞一张票,并给他找零");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else if ("关羽".equals(name)){
System.out.println("关羽给售票员一张五元");
fiveCount ++; //关羽给售票员一张五元
} else if("刘备".equals(name)){
System.out.println("刘备给售票员一张五元");
fiveCount ++;// 刘备给售票员一张五元
}
// 唤醒进程的条件
if (fiveCount == 3){
notifyAll();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
buy();
}
public static void main(String[] args){
Ticket ticket = new Ticket();
Thread th1 = new Thread(ticket);
Thread th2 = new Thread(ticket);
Thread th3 = new Thread(ticket);
th1.setName("张飞");
th2.setName("关羽");
th3.setName("刘备");
th1.start();
th2.start();
th3.start();
}
}
//输出结果为:
没有足够多的零钱,需要等待有零钱之后才能把票卖给张飞
刘备给售票员一张五元
关羽给售票员一张五元
卖给张飞一张票,并给他找零
两个线程如何交替打印a-z
public class TrigglePrint implements Runnable{
char c = 'a';
public synchronized void print(){
System.out.println(Thread.currentThread().getName() + ": " + c);
notifyAll();
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
for (; c < 'z'; c++){
print();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TrigglePrint tp = new TrigglePrint();
Thread thread1 = new Thread(tp);
Thread thread2 = new Thread(tp);
thread1.start();
thread2.start();
}
}
//结果:
Thread-0: a
Thread-1: a
Thread-0: b
Thread-1: c
Thread-0: d
Thread-1: e
Thread-0: f
Thread-1: g
Thread-0: h
Thread-1: i
Thread-0: j
...
...