进程的特征是:
线程的特征是:
同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈。
线程和进程的主要差别:
class TestThread{
public void run(){
for(int i = 0; i < 10; ++i)
System.out.println("TestThread is running");
}
}
class ThreadDemo1 {
public static void main(String[] args){
new TestThread().run();
for(int i = 0; i < 10; ++i)
System.out.println("main 线程在运行");
}
}
输出:(要执行main,必须等 Test运行完)
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
class TestThread extends Thread{ // 继承 Thread 类
public void run(){
for(int i = 0; i < 10; ++i)
System.out.println("TestThread is running");
}
}
class ThreadDemo1 {
public static void main(String[] args){
new TestThread().start(); // 使用 start 启动
for(int i = 0; i < 10; ++i)
System.out.println("main 线程在运行");
}
}
输出:
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
class TestThread implements Runnable{ // 多线程实现类
public void run(){
for(int i = 0; i < 10; ++i)
System.out.println("TestThread is running");
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
new Thread(t).start();//启动多线程
for(int i = 0; i < 10; ++i)
System.out.println("main 线程在运行");
}
}
输出:
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
main 线程在运行
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
TestThread is running
main 线程在运行
main 线程在运行
class TestThread extends Thread {
private int tickets = 20;
public void run(){
while(true){
if(tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售出一张票,剩余票数:" + --tickets);
}
}
}
class ThreadDemo1 {
public static void main(String[] args){
new TestThread().start();
new TestThread().start();
new TestThread().start();
new TestThread().start();
}
}
输出:(部分结果)
Thread-1售出一张票,剩余票数:19
Thread-0售出一张票,剩余票数:19
Thread-1售出一张票,剩余票数:18
Thread-0售出一张票,剩余票数:18
Thread-1售出一张票,剩余票数:17
Thread-2售出一张票,剩余票数:19
Thread-0售出一张票,剩余票数:17
Thread-3售出一张票,剩余票数:19
Thread-3售出一张票,剩余票数:18
Thread-3售出一张票,剩余票数:17
Thread-3售出一张票,剩余票数:16
结论:继承Thread,不能达到线程资源共享
class TestThread implements Runnable {
private int tickets = 20;
public void run(){
while(true){
if(tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售出一张票,剩余票数:" + --tickets);
}
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
输出:(完整)
Thread-1售出一张票,剩余票数:19
Thread-3售出一张票,剩余票数:16
Thread-3售出一张票,剩余票数:14
Thread-3售出一张票,剩余票数:13
Thread-3售出一张票,剩余票数:12
Thread-3售出一张票,剩余票数:11
Thread-3售出一张票,剩余票数:10
Thread-2售出一张票,剩余票数:17
Thread-0售出一张票,剩余票数:18
Thread-0售出一张票,剩余票数:7
Thread-0售出一张票,剩余票数:6
Thread-0售出一张票,剩余票数:5
Thread-2售出一张票,剩余票数:8
Thread-3售出一张票,剩余票数:9
Thread-3售出一张票,剩余票数:2
Thread-3售出一张票,剩余票数:1
Thread-3售出一张票,剩余票数:0
Thread-1售出一张票,剩余票数:15
Thread-2售出一张票,剩余票数:3
Thread-0售出一张票,剩余票数:4
结论:实现 Runnable 可以实现资源共享,这种方法更具优势!
getName(), setName()
class TestThread implements Runnable {
private int tickets = 20;
public void run(){
while(true){
if(tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售出一张票,剩余票数:" + --tickets);
}
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.setName("站点1");
Thread t2 = new Thread(t);
t2.setName("站点2");
Thread t3 = new Thread(t);
t3.setName("站点3");
Thread t4 = new Thread(t);
t4.setName("站点4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出:
站点1售出一张票,剩余票数:19
站点4售出一张票,剩余票数:16
站点3售出一张票,剩余票数:17
站点2售出一张票,剩余票数:18
站点2售出一张票,剩余票数:12
站点2售出一张票,剩余票数:11
站点2售出一张票,剩余票数:10
站点2售出一张票,剩余票数:9
站点2售出一张票,剩余票数:8
站点3售出一张票,剩余票数:13
站点3售出一张票,剩余票数:6
站点3售出一张票,剩余票数:5
站点3售出一张票,剩余票数:4
站点4售出一张票,剩余票数:14
站点4售出一张票,剩余票数:2
站点4售出一张票,剩余票数:1
站点1售出一张票,剩余票数:15
站点4售出一张票,剩余票数:0
站点3售出一张票,剩余票数:3
站点2售出一张票,剩余票数:7
isAlive()
System.out.println(t1.isAlive()); // false
System.out.println(t2.isAlive());// false
System.out.println(t3.isAlive());// false
System.out.println(t4.isAlive());// false
t1.start();
t2.start();
t3.start();
t4.start();
System.out.println(t1.isAlive());// true
System.out.println(t2.isAlive());// true
System.out.println(t3.isAlive());// true
System.out.println(t4.isAlive());// true
调用 start()
之前,调用 setDaemon(true)
方法,这个线程就变成了后台进程
class TestThread implements Runnable {
private int tickets = 20;
public void run(){
while(true){
if(tickets > 0)
System.out.println(Thread.currentThread().getName()
+ "售出一张票,剩余票数:" + --tickets);
}
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.setName("站点1");
Thread t2 = new Thread(t);
t2.setName("站点2");
Thread t3 = new Thread(t);
t3.setName("站点3");
Thread t4 = new Thread(t);
t4.setName("站点4");
t1.setDaemon(true);//后台程序
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true);
System.out.println(t1.isAlive());
System.out.println(t2.isAlive());
System.out.println(t3.isAlive());
System.out.println(t4.isAlive());
t1.start();
t2.start();
t3.start();
t4.start();
System.out.println(t1.isAlive());
System.out.println(t2.isAlive());
System.out.println(t3.isAlive());
System.out.println(t4.isAlive());
}
}
输出:
false
false
false
false
true
true
true
true
站点3售出一张票,剩余票数:19
站点3售出一张票,剩余票数:16
站点1售出一张票,剩余票数:17
站点2售出一张票,剩余票数:18
站点2售出一张票,剩余票数:12
站点1售出一张票,剩余票数:13
站点1售出一张票,剩余票数:10
进程已结束,退出代码0
结论:整个进程中,只有后台线程运行时,进程就会结束
class TestThread implements Runnable {
public void run(){
int i = 0;
for(int x = 0; x < 10; ++x)
System.out.println(Thread.currentThread().getName() + "-->" + i++);
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.start();
int i = 0;
for(int x = 0; x < 10; ++x){
if(i == 5){
try{
t1.join(); // 运行完以后,才能运行后面的
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
System.out.println("main Thread" + i++);
}
}
}
输出:
以下总在 t1 线程 join 以后,全部运行完了,才开始运行 main,在此之前,main 等待
main Thread5
main Thread6
main Thread7
main Thread8
main Thread9
try {
t1.sleep(800);
}
catch (InterruptedException e){
}
class TestThread implements Runnable {
public void run(){
try{
System.out.println("休眠10s");
Thread.sleep(10000);
System.out.println("run, 继续运行");
}
catch(InterruptedException e){
System.out.println("run, 中断");
return;
}
System.out.println("run, 休眠后继续");
System.out.println("run, 正常结束");
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.start();
int i = 0;
try {
Thread.sleep(2000);
}
catch (InterruptedException e){
}
System.out.println("在main中, 中断t1");
System.out.println(t1.isInterrupted());
t1.interrupt();
System.out.println(t1.isInterrupted());
System.out.println("main 结束");
}
}
输出:
休眠10s
在main中, 中断t1
false
true
run, 中断
main 结束
class TestThread implements Runnable {
private int tickets = 20;
public void run() {
while (true) {
if (tickets > 0) {
try {
Thread.sleep(500);
// 某线程休息了,之前 tickets > 0
// CPU 给到别的线程 卖出去票了
// 休息完回来,可能没有票了,但是该线程已经进入了 if 块,
// 该线程又卖出一些(可能卖多了,超出实际)
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()
+ "售出一张票,剩余票数:" + --tickets);
}
}
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.setName("站点1");
Thread t2 = new Thread(t);
t2.setName("站点2");
Thread t3 = new Thread(t);
t3.setName("站点3");
Thread t4 = new Thread(t);
t4.setName("站点4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出:
站点3售出一张票,剩余票数:18
站点1售出一张票,剩余票数:18
站点2售出一张票,剩余票数:17
站点4售出一张票,剩余票数:19
站点1售出一张票,剩余票数:16
站点4售出一张票,剩余票数:14
站点2售出一张票,剩余票数:15
站点3售出一张票,剩余票数:13
站点1售出一张票,剩余票数:12
站点3售出一张票,剩余票数:10
站点4售出一张票,剩余票数:9
站点2售出一张票,剩余票数:11
站点3售出一张票,剩余票数:8
站点1售出一张票,剩余票数:8
站点2售出一张票,剩余票数:7
站点4售出一张票,剩余票数:6
站点1售出一张票,剩余票数:5
站点3售出一张票,剩余票数:4
站点2售出一张票,剩余票数:3
站点4售出一张票,剩余票数:2
站点3售出一张票,剩余票数:1
站点1售出一张票,剩余票数:1
站点4售出一张票,剩余票数:-1
站点2售出一张票,剩余票数:0
站点1售出一张票,剩余票数:-2
站点3售出一张票,剩余票数:-3
原因:资源数据访问不同步
某些代码,任何时候只能有一个线程在占用执行
只有该线程离开同步代码块后,其他线程才能进入同步代码块
class TestThread implements Runnable {
private int tickets = 20;
public void run() {
while (true) {
synchronized (this) { // 同步代码块
if (tickets > 0) {
try {
Thread.sleep(50);
// 某线程休息了,之前 tickets > 0
// 别的线程 卖出去票了
// 休息完回来,可能没有票了,但是线程已经进入了 if 块
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()
+ "售出一张票,剩余票数:" + --tickets);
}
}
}
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.setName("站点1");
Thread t2 = new Thread(t);
t2.setName("站点2");
Thread t3 = new Thread(t);
t3.setName("站点3");
Thread t4 = new Thread(t);
t4.setName("站点4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出:
站点1售出一张票,剩余票数:19
站点1售出一张票,剩余票数:18
站点1售出一张票,剩余票数:17
站点1售出一张票,剩余票数:16
站点1售出一张票,剩余票数:15
站点1售出一张票,剩余票数:14
站点1售出一张票,剩余票数:13
站点1售出一张票,剩余票数:12
站点4售出一张票,剩余票数:11
站点4售出一张票,剩余票数:10
站点4售出一张票,剩余票数:9
站点4售出一张票,剩余票数:8
站点4售出一张票,剩余票数:7
站点4售出一张票,剩余票数:6
站点4售出一张票,剩余票数:5
站点4售出一张票,剩余票数:4
站点4售出一张票,剩余票数:3
站点4售出一张票,剩余票数:2
站点4售出一张票,剩余票数:1
站点4售出一张票,剩余票数:0
在方法前面加上 synchronized
class TestThread implements Runnable {
private int tickets = 20;
public void run() {
while (true) {
sale();
}
}
public synchronized void sale(){ // 同步方法
if (tickets > 0) {
try {
Thread.sleep(100);
// 某线程休息了,之前 tickets > 0
// 别的线程 卖出去票了
// 休息完回来,可能没有票了,但是线程已经进入了 if 块
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()
+ "售出一张票,剩余票数:" + --tickets);
}
}
}
class ThreadDemo1 {
public static void main(String[] args){
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.setName("站点1");
Thread t2 = new Thread(t);
t2.setName("站点2");
Thread t3 = new Thread(t);
t3.setName("站点3");
Thread t4 = new Thread(t);
t4.setName("站点4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
最常见的例子,线程1在对象A中(线程1持有锁),目前在等对象B解锁,线程2在对象B中,正在等待对象A解锁,这样,两个线程都无法进行下一步,称之死锁
如何避免:
死锁例子:
class A{
synchronized void funA(B b){
String name = Thread.currentThread().getName();
System.out.println(name + "进入 funA");
try{
Thread.sleep(1000);
}
catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(name + "调用B类的last()");
b.last();
}
synchronized void last()
{
System.out.println("A 类的last()方法");
}
}
class B{
synchronized void funB(A a){
String name = Thread.currentThread().getName();
System.out.println(name + "进入 funB");
try{
Thread.sleep(1000);
}
catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(name + "调用A类的last()");
a.last();
}
synchronized void last()
{
System.out.println("B 类的last()方法");
}
}
class DeadLockDemo implements Runnable{
A a = new A();
B b = new B();
DeadLockDemo(){ // 构造函数
Thread.currentThread().setName("Main->>Thread");
new Thread(this).start();
a.funA(b);
System.out.println("main 线程运行结束");
}
public void run(){
Thread.currentThread().setName("Test->>Thread");
b.funB(a);
System.out.println("其他线程运行完毕");
}
public static void main(String [] args){
new DeadLockDemo();
}
}
输出:
Main->>Thread进入 funA
Test->>Thread进入 funB
Main->>Thread调用B类的last()
Test->>Thread调用A类的last()
两个last()
都是同步的代码,两个线程各执一个锁,等待对方离开+解锁,僵持不下。
例子:
class Producer implements Runnable{
Pdata p = null;
public Producer(Pdata p){
this.p = p;
}
public void run(){
int i = 0;
while(true){
// 往数据库里存放数据
if(i == 0)
{
p.name = "张三";
p.sex = "男";
}
else
{
p.name = "李四";
p.sex = "女";
}
i = (i+1)%2;
}
}
}
class Consumer implements Runnable{
Pdata p = null;
public Consumer(Pdata p){
this.p = p;
}
public void run(){
while(true){
// 从数据库里读取数据
System.out.println("消费者读取数据:"+p.name+"-->"+p.sex);
}
}
}
class Pdata{
String name;
String sex;
}
class test9{
public static void main(String[] args){
Pdata p = new Pdata();
new Thread(new Producer(p)).start();
new Thread(new Consumer(p)).start();
}
}
输出:
消费者读取数据:张三-->女
消费者读取数据:李四-->女
消费者读取数据:张三-->女
消费者读取数据:李四-->男
消费者读取数据:张三-->男
两个线程共同操作一个 数据类,一个还没操作完,另一个就把数据取走了。
修改:
set()、get()
class Producer implements Runnable{
Pdata p = null;
public Producer(Pdata p){
this.p = p;
}
public void run(){
int i = 0;
while(true){
// 往数据库里存放数据
if(i == 0)
{
p.set("张三", "男");
}
else
{
p.set("李四", "女");
}
i = (i+1)%2;
}
}
}
class Consumer implements Runnable{
Pdata p = null;
public Consumer(Pdata p){
this.p = p;
}
public void run(){
while(true){
// 从数据库里读取数据
p.get();
}
}
}
class Pdata{
String name;
String sex;
public synchronized void set(String name, String sex){
this.name = name;
this.sex = sex;
}
public synchronized void get(){
System.out.println("消费者读取数据:"+this.name+"-->"+this.sex);
}
}
class test9{
public static void main(String[] args){
Pdata p = new Pdata();
new Thread(new Producer(p)).start();
new Thread(new Consumer(p)).start();
}
}
输出:没有差错。
消费者读取数据:张三-->男
消费者读取数据:张三-->男
消费者读取数据:张三-->男
消费者读取数据:张三-->男
...
解决上面问题:需要线程间通信
Java是通过 Object类 的 wait、 notify、 notifyall
这几个方法来实现线程间的通信的,又因为
所有的类都是从 Object 继承的,任何类都可以直接使用这些方法。
wait
:告诉当前线程放弃监视器并进入睡眠状态,直到其他线程进入同一监视器并调用 notify
为止notify
:唤醒同一对象监视器中调用 wait
的第1个线程。这类似排队买票,一个人买完之后,
后面的人才可以继续买notifyall
:唤醒同一对象监视器中调用 wait
的所有线程,具有最高优先级的线程首先被唤醒
并执行修改:在数据类中定义一个新的成员变量 bFull 来表示数据存储空间的状态
class Producer implements Runnable{
Pdata p = null;
public Producer(Pdata p){
this.p = p;
}
public void run(){
int i = 0;
while(true){
// 往数据库里存放数据
if(i == 0)
{
p.set("张三", "男");
}
else
{
p.set("李四", "女");
}
i = (i+1)%2;
}
}
}
class Consumer implements Runnable{
Pdata p = null;
public Consumer(Pdata p){
this.p = p;
}
public void run(){
while(true){
// 从数据库里读取数据
p.get();
}
}
}
class Pdata{
String name;
String sex;
boolean bFull = false;
public synchronized void set(String name, String sex){
if(bFull){
try{
wait(); // 写满了,后来写的线程要等待
}
catch (InterruptedException e)
{}
}
notify(); // 可以写了,唤醒最先到达的线程
this.name = name;
this.sex = sex;
this.bFull = true;
try {
Thread.sleep(1000);//让控制台输出慢一点
}
catch (Exception e){
System.out.println(e.getMessage());
}
}
public synchronized void get(){
if(!bFull){
try{
wait();//还没写满,需要读取?等着
}
catch (InterruptedException e)
{}
}
notify(); // 写满了,可以读取了,唤醒最先到达的线程
System.out.println("消费者读取数据:"+this.name+"-->"+this.sex);
this.bFull = false;
}
}
class test9{
public static void main(String[] args){
Pdata p = new Pdata();
new Thread(new Producer(p)).start();
new Thread(new Consumer(p)).start();
}
}
输出:(正常)
消费者读取数据:张三-->男
消费者读取数据:李四-->女
消费者读取数据:张三-->男
消费者读取数据:李四-->女
消费者读取数据:张三-->男
消费者读取数据:李四-->女
消费者读取数据:张三-->男
...
wait、 notify、 notifyall
这3个方法只能在 synchronized 方法中调用,即无论线程调用一个对
象的 wait
还是 notify
方法,该线程必须先得到该对象的锁标记。
这样, notify
就只能唤醒同一对象监视器中调用 wait
的线程。
而使用多个对象监视器,就可以分别有多个 wait、 notify 的情况,同组里的 wait 只能被同组的 notify 唤醒。
有些方法不推荐使用:
suspend()
、resume()
、stop()
方法stop
会停止正在操作数据的过程,会导致数据不完整如何控制?看下面代码
class TestThread1 implements Runnable{
private boolean flag = true;
public void stopMe(){
flag = false;
}
public void run(){
while(flag){
System.out.println(Thread.currentThread().getName()+"线程在运行");
}
}
}
class ThreadLifeDemo {
public static void main(String[] args){
TestThread1 t = new TestThread1();
new Thread(t).start();
for(int i = 0; i < 20; ++i){
if(i == 5)
t.stopMe();
System.out.println("main线程在运行"+i);
}
}
}
输出:
main线程在运行0
Thread-0线程在运行
main线程在运行1
Thread-0线程在运行
Thread-0线程在运行
Thread-0线程在运行
Thread-0线程在运行
main线程在运行2
main线程在运行3
main线程在运行4
main线程在运行5
main线程在运行6
Thread-0线程在运行
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
进程已结束,退出代码为 0
解释: i = 5 的时候,通过将线程 run 方法里的循环条件破坏,run 结束,线程也就结束 当 i = 5 的时候,调用了 stopMe 后,CPU 不一定会马上切换到 Thread-0 线程上,计数器 i 还可能继续累加,之后 CPU 切换到 Thread-0 后,该线程才真正结束
结论:
run()
方法中循环条件的方式来结束一个线程,是实际中用的最多的方法