------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
概念
进程:正在执行中的程序。
每个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:进程中一个独立的控制单元。
线程控制着进程的执行。
一个进程中至少有一个线程。
l 创建线程的第一种方法。
步骤:
1. 创建类继承Thread
2. 重写Thread的run方法。
3. 调用线程的start方法。该方法两个作用:1.启动线程,2.调用该线程run方法
*** start方法与run方法:
start方法启动了线程,调用该线程的run方法。
run方法只是对象调用方法。虽然线程创建了,但是没有被运行。
l 线程的创建过程
l 获取线程对象以及名称
getName() Thread-[0,1... ...]
currentThread() static
设置线程的名称:setName() 或者 构造函数调用super(String Name)
l 售票窗口卖票的例子(创建线程的第二种方法)
四个线程同时卖票
一、继承Thread方法创建线程。
public static void main(String[] args) {
// 注意:如果没有使用static关键词,则每一次都会创建一个ticket=100。
TicketTest t1 = new TicketTest("窗口一");
TicketTest t2 = new TicketTest("窗口二");
TicketTest t3 = new TicketTest("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class TicketTest extends Thread{
// 该构造方法,通过调用父类方法,直接给将要创建的新的线程赋予新的名字。
public TicketTest(String name) {
super(name);
}
// 使用static关键字,将ticket变为静态。
//private static int ticket = 100;
private int ticket = 100;
// 必须要重写Thread类的run方法。
public void run() {
while(true) {
if(ticket > 0)
System.out.println(Thread.currentThread().getName()+ "还剩:"+(ticket--)+" 张票");
}
}
}
*注: 如果一个线程通过start()方法开始运行了,再多余的start()已经没有意义了,则会报错:
t1.start();
t1.start();
t1.start();
java.lang.IllegalThreadStateException
二、实现Runnable接口创建线程。
public static void main(String[] args) {
TicketTest tt = new TicketTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
Thread t3 = new Thread(tt);
t1.start();
t2.start();
t3.start();
}
}
class TicketTest implements Runnable /*extends Thread*/{
private int ticket = 100;
// 必须要重写Thread类的run方法。
public void run() {
while(true) {
if(ticket > 0)
System.out.println(Thread.currentThread().getName()+ "还剩:"+(ticket--)+" 张票");
}
}
}
创建线程的第二种方法
1. 实现Runnable接口
2. 实现Runnable接口的run方法
3. 创建一个Thread的Thread实例
4. 将实现了Runnable接口的类作为对象传递给创建的Thread实例
5. 调用Thread实例的start方法。
*注:大多数情况下,如果只重写run方法,二部重写Thread的其他类,那么应该使用Runnable接口。
l 区别:implement Runnable & extends Thread
1. 避免了java只能单继承。
2. 实现Runnable接口的类的run方法写在Runnable子类中。
3. 继承Thread类的类的run方法写在Thread子类中。
l 多线程的安全问题
while(true) {
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "还剩:"+(ticket--)+" 张票");
}
}
说明:当票数被剪掉以前,所有的线程都经过ticket > 0判断语句后被分配到了执行权,停止在票数剪掉之前的地方,那么都将会执行ticket--的代码,从而使ticket的数目小于0。
解决方法1:
l 同步
实现同步的两个先决条件:
1. 大于等于两个线程。
2. 所有线程使用同一个锁。(对于同时包含同步代码和同步函数的情况,同步代码使用this锁;静态同步方法使用该类的字节码对象)
(一)同步代码块:
格式:
synchronized(对象) {
...代码
}
对变量进行判断和操作的代码进行同步操作。
private int ticket = 100;
// 这个对象相当于锁,这个锁要放在run方法以外,让所有的线程来共享这个锁。
Object obj = new Object();
// 必须要重写Thread类的run方法。
public void run() {
while(true) {
// 同步代码块
synchronized(obj) {
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println(Thread.currentThread().getName()+ "还剩:"+(ticket--)+" 张票");
}
}
}
}
(二)同步函数
格式:
public synchronized xxx() {
...
}
同步函数的锁旗标是this
同步静态方法
使用的锁是该类所属的字节码文件对象,即 类名.class
l 死锁
当两个线程同时获取了一个不同锁Lock1与Lock2,但下面的各线程又将要获取对方手中的锁,这种情况下,程序会停滞住。
有时会出现和谐的现象。
public class TestDeadLock{
public static void main(String[] args) {
DeadLock dl = new DeadLock();
Thread t1 = new Thread(dl);
Thread t2 = new Thread(dl);
dl.flag = true;
t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
dl.flag = false;
t2.start();
}
}
class Lock {
static Object lock1 = new Object();
static Object lock2 = new Object();
}
class DeadLock implements Runnable {
public boolean flag = true;
public void run() {
if(flag == true) {
while(true){
synchronized(Lock.lock1) {
System.out.println("if中的Lock1");
synchronized(Lock.lock2) {
System.out.println("if中的Lock2");
}
}
}
}
else {
while(true){
synchronized(Lock.lock2) {
System.out.println("else中的Lock2");
synchronized(Lock.lock1) {
System.out.println("else中的Lock1");
}
}
}
}
}
}
输出结果:
l 线程间的通信
l wait() - notify() 机制。
当线程在运行时,会产生一个线程池,所有wait的线程都会放到线程池中,notify()方法会唤醒线程池中的第一个线程。
package com.lxh.thread;
/**
* 解决线程同步的两个先决条件
* 1. 两个或两个以上的线程
* 2. 同一个锁
* 在这个例子中,被共享操作的资源分布在两个线程的run方法中,
* 所以对着两部分代码的共享资源,都要进行同步。
* */
public class InputOutputDemo {
public static void main(String[] args) {
Source s = new Source();
Input input = new Input(s);
Output output = new Output(s);
Thread t1 = new Thread(input);
Thread t2 = new Thread(output);
t1.start();
t2.start();
}
}
class Input implements Runnable {
private Source s ;
public Input(Source s) {
this.s = s;
}
private int i;
public void run() {
while(true) { //对同一个对象的变量进行无限次的赋值
synchronized(s){
if(s.flag)
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(i == 1) {
s.place = "DaTong";
s.color = "red";
i++;
} else {
s.place = "攀枝花";
s.color = "黑色";
i++;
}
s.flag = true;
s.notify();
}
i = i%2;
}
}
}
class Output implements Runnable {
private Source s ;
public Output(Source s) {
this.s = s;
}
public void run() {
while(true){ 对同一个对象的变量进行无限次的取值
synchronized(s){
if(!s.flag)
try {
s.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("产地:" + s.place + " 颜色:" + s.color );
s.flag = false;
s.notify();
}
}
}
}
class Source {
public String place;
public String color;
public boolean flag;
}
***注意:
只有同一个锁上的同一个wait的线程才能被同一个锁上的notify线程锁唤醒。不可以对不同锁中的线程进行唤醒。
也就是说:等待和唤醒是同一把锁。
而锁可以是任意对象,所以notify