1)、自定义线程,继承Thread类,重写run方法
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i);
}
}
}
2)、启动线程
public class Demo {
public static void main(String[] args) {
//1.启动线程
MyThread t = new MyThread();
t.start();
//2.主线程继续
for (int k = 0; k < 100; k++) {
System.out.println("k = " + k);
}
}
}
注意:
重写的是run方法,但是启动线程使用start方法
对于一个线程对象,只能调用一次start方法启动线程
对于一个线程类,可以创建多个线程对象,每个线程对象都可以独立的进行运行
1)、Thread类的构造方法:
public Thread()
:分配一个新的线程对象。
public Thread(String name)
:分配一个指定名字的新的线程对象。
public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。(使用实现Runnable接口的对象)
public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
2)、常用方法:
public String getName()
:获取当前线程名称。
public String setName()
:设置线程名称
任何一个线程对象都有一个默认名称:Thread-[索引]
public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。
public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。静态方法
public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。静态方法
1)、自定义线程,实现Runnable接口,重写run方法
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i);
}
}
}
2)、启动线程时要先创建一个自定义的类,再创建Thread对象
public class Demo {
public static void main(String[] args) {
//1.创建自定义类对象
MyRunnable myRun = new MyRunnable();
//2.创建一个Thread对象
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
Thread t3 = new Thread(myRun);
t1.start();
t2.start();
t3.start();
for (int k = 0; k < 100; k++) {
System.out.println("k = " + k);
}
}
}
注意:
自定义类实现了Runnable接口,并没有继承自Thread,所以不是一个线程
当启动线程时,仍然需要创建Thread对象
将一个自定义类的对象传给多个Thread对象
1)、第一种方式需要子类继承Thread,因为只能单继承,所以子类就会很不方便
2)、第二种方式需要实现Runable接口,Java允许子类多实现多个接口,所以比较方便(多使用)
1)、方式一(Thread的匿名子类):创建Thread子类,创建子类对象,子类对象.start()三步合一
new Thread(){//子类类体}.start();
例:
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i);
}
}
}.start();
2)、方式二:(Runnable的匿名子类):创建实现Runnable接口子类,创建Thread对象,Thread对象构造方法接收Runnable子类对象,Thread.start()四步合一
new Thread(new Runnable(){//Runnable子类类体}).start;
例:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i);
}
}
}).start();
当多线程共同访问同一共享资源时,就会产生安全性的问题(一个进程没有彻底访问结束这个资源,另一个进程就要进来,就使得结果资源不完整)
例:
实现Runnable类:
public class MyRunnable implements Runnable {
public int money = 0;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
money++;
}
}
}
测试类:
public class Demo {
public static void main(String[] args) throws
InterruptedException {
MyRunnable myRun = new MyRunnable();
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
t1.start();
t2.start();
System.out.println("主线程休息1秒,目的让两个线程执行完毕...");
Thread.sleep(1000);
System.out.println("num的最终结果是:" + myRun.money);
}
}
t1和t2一起访问 money,一起对money进行操作,结果本该是2000,但是在俩个线程运行时,有时t1还没有彻底对money操作完成,线程t2就要对money进行操作,这使得t1对money的操作丢失,造成数据不安全的问题(在线程中对一个数据操作的越久,这种问题就越突出)
在线程中定义一个对数据的操作方法时使用synchronized进行修饰,保证一个线程进入时,其它后续线程在后面排队,从而使得数据安全
public class Tickets implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
int t = 0;
try {
t = getTicket();
} catch (InterruptedException e) {//方法中的sleep()导致的异常
e.printStackTrace();
}
if (t == -1) {
break;//结束循环
}
System.out.println(Thread.currentThread().getName() + "取走一张票:" + t);
}
}
//synchronized(同步)关键字保证一个线程进入时,其它后续线程在后面排队。
private synchronized int getTicket() throws InterruptedException {//sleep异常
if (tickets > 0) {
int n = tickets;
Thread.sleep(1);//目的让错误更明显
tickets--;
return n;
}else{
return -1;
}
}
}
语法:
synchronized(锁对象){
//同步代码
}
例:(锁对象可以是任何对象)
public class Tickets implements Runnable {
private int tickets = 100;
//做为"锁"对象的,可以是任何对象。
private Object obj = new Object();
@Override
public void run() {
while (true) {
//必须保证多个“线程”使用的是同一把锁
synchronized (obj) {//窗口2,窗口3
//窗口1--进入后,加锁
if (tickets <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + " 来取走一张票:" + tickets);
tickets--;
}
}
}
}
多个线程使用同一个锁才能达到数据安全的效果
1)、语法
Lock l = ...;
l.lock(); //加锁
try {
// access the resource protected by this lock
} finally {
l.unlock(); //解锁
}
2)、例:
public class Tickets implements Runnable {
private int tickets = 100;
//定义一个Lock锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();//加锁
try {
if (tickets <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+ " 来取走一张票:" + tickets);
tickets--;
}finally {
lock.unlock();//解锁
}
}
}
}
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
1)、新建:MyThread t= new MyThread();
2)、可运行(待运行):t.start();调用该方法时,该线程可能在运行,可能没有运行,这取决于操作系统
3)、锁阻塞:一个线程运行后调用某个方法,但是此方法正在被其他线程使用且上了锁
4)、无限等待:线程开启后,调用了.wait()方法,暂停运行
5)、计时等待:线程开启后,调用sleep()方法,等待sleep中的时间结束后,线程在次进入待运行
6)、被终止:run()方法正常运行完毕
调用线程的wite()方法吗,使该线程进入等待;
其他线程调用notify()方法,使在等待的线程唤醒;
例:
public class Demo {
private static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (obj) {
System.out.println("i = " + i);
if (i == 20) {
try {
//当前持有这把锁的线程,开始无限等待....
System.out.println("线程开始进入无限等待....");
obj.wait();
System.out.println("线程被唤醒.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}.start();
System.out.println("主线程休息2秒...目的让前面的线程先运行...");
Thread.sleep(2000);
new Thread(){
@Override
public void run() {
synchronized (obj) {
//唤醒所有obj锁上等待的线程
System.out.println("我要唤醒所有等待的线程....");
obj.notifyAll();
}
}
}.start();
}
}
[ ] 能够描述Java中多线程运行原理
在栈中,每个线程都有其独立的栈区;
[ ] 能够使用继承类的方式创建多线程
class MyThread entends Thread{
public void run(){
}
}
[ ] 能够使用实现接口的方式创建多线程
class MyRunnable implements Runnable{
public void run(){
}
}
main(){
MyRunnable myRun = new MyRunnable();
Thread t = new Thread(myRun);
t.start();
}
[ ] 能够说出实现接口方式的好处
多实现,继承只能单继承
[ ] 能够解释安全问题的出现的原因
多个线程访问同一个资源,对这个资源进行操作,当一个线程还没有完成对这个数据的操作,下一个线程就要进入,使得上一次的操作的数据丢失
[ ] 能够使用同步代码块解决线程安全问题
synchronized (锁对象){线程内代码}
[ ] 能够使用同步方法解决线程安全问题
public synchronized void show(){
//同步代码
}
[ ] 能够说出线程6个状态的名称
1)、新建
2)、可运行
3)、锁阻塞
4)、无线等待
5)、及时等待
6)、被终止