中国游泳队棒棒哒~
一、介绍
进程是a set of instructions which would be executed in sequence
线程是一个进程内部的顺序控制流(程序的不同执行路径)
进程 VS 线程
- 每个进程都有独立的代码和数据空间(进程上下文),是资源分配单位,线程共享进程的资源,进程间的切换会有很大的开销
- 线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,但是每个线程有独立的运行栈和程序计数器,线程切换开销小
- 多进程,如OS同时执行多个应用任务
- 多线程,如同一个Application中有多个顺序流同时执行
二、JAVA中的实现
JAVA线程通过java.lang.Thread类来实现,我们可以通过它的实例来创建新的线程,每个线程都是通过线程对象所对应的run方法来定义操作的内容,run方法成为线程体,然后通过start方法来启动进程(不直接调用run,因为那样就变成了方法调用,并未开始线程,此时线程is not alive),VM启动时会调用一个由主方法main所定义的线程
Mark: 其实应用程序使用java的Executor框架来创建线程池~
1、Runnable接口
Runnable接口只有一个方法public void run()用来定义线程运行体,方法中可以调用Thread类的静态方法,如public static Thread currentThread()获取当前线程的引用。Thread thread = new Thread(target) 启动一个线程,target是Runnable接口类型对象(实现run方法):
public void run() {
if (target !=null) {
target.run();
}
}
Runnable接口可以为多个线程提供共享数据:
public class T1 {
public static void main(String[] args) {
Runner rn =new Runner();
Thread t =new Thread(rn);
Thread t1 =new Thread(rn);
t.start();//启动线程
t1.start();//启动线程
}
}
class Runner implements Runnable {
int num = 0;
public void run() {
num ++;
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
结果:Thread-0 2
Thread-1 2
2、Thread类
我们可以继承Thread类,然后重写run方法,因为继承Thread类的子类本身就是一个Thread类型,我们可以直接实例化子类创建线程
public class T1 {
public static void main(String[] args) {
Runner rn =new Runner();
Runner rn1 =new Runner();
rn.start();
rn1.start();
System.out.println(Thread.currentThread().getName());
}
}
class Runner extends Thread {
int num = 0;
public void run() {
num ++;
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
main
Thread-1 1
Thread-0 1
3、实现Runnable接口 VS 继承Thread类
我们可以继承Thread类或者实现Runnable接口来实现线程运行体,最好用实现Runnable接口,因为这样可以“多实现”(实现多个),而子类只能够是单继承
- 静态方法sleep(),会抛出异常,因为可能被打断(重写方法时不能throw出不同的异常)
interrupt改变中断的状态,而并不能中断一个运行的线程,是给受阻塞的线程抛出一个中断信号,让受阻线程退出阻塞的状态,比如进程被Object.wait, Thread.join和Thread.sleep阻塞(这个期间会不断的检查中断状态值),就可以用interrupt中断这个阻塞线程,并抛出InterruptedException,如果线程没有被阻塞,interrupt则无效。final stop方法(已经被废弃了),比interrupt方法粗暴,立马死掉,连处理异常的机会都没有(对于interrupt异常,我们还可以处理异常,比如关闭某个打开的资源等),但是如果进程死锁当机,也只能stop了~
我们希望可以循环执行线程运行体,直到某个点停止,此时我们可以使用flag=true, while(flag),然后在某个合适的地方将flag复制为false~
- join(),合并线程,先执行完一个再执行另外一个
- yield(),表示退让一下(让出CPU一下)
- 优先级
线程调度器会监控程序中启动后进入就绪状态的所有线程,线程调度器按照线程的优先级决定应该优先调度哪一个线程,优先级的范围是1~10,缺省5
静态变量:
MIN_PRIORITY = 1
MAX_PRIORITY = 10
NORM_PRIORITY = 5
int getPriority() //返回优先级
void setPriority(int priority) //设置优先级
三、线程同步
小明和阿华共同知道某张卡的账号和密码(卡里只有3000块钱),然后小明去ATM机上取2000块钱,而阿华同时想要网上转账给小黄,此时ATM刚验证了卡里有>=2000的资金决定给小明钱的时候,阿华正好转了2000给小黄了,3000还没有减去2000的时候,ATM又吐了2000给小明,此时还是3000-2000,然后账号还剩1000。。。这样不就乱了吗?我们该怎么做?保证一个人取钱的时候,另一个人不能同时取不就行了~
synchronized 方法 {
执行方法的过程中,当前调用者(类实例对象)被锁定
}
方法 {
synchronized(this) {
当执行某区域的过程过,不会被另一个线程打断
}
}
每个对象都带有一个内存锁,类也有一个
两个没什么区别,只是粒度大小的问题,synchronized表示必须获取当前对象的锁才能执行,出了作用域就是释放锁。访问一个对象的同步代码时,就不能访问这个对象的其他同步代码,但是可以访问这个对象的非同步代码,yooooo~
我们可以同步静态方法和其他任意引用对象~
synchronized(Object A) {
A.method();
}
例子:
四、死锁
标记锁,这个标记保证在任意一个时刻,只有一个线程访问该对象,保证共享数据操作的完整性,每个对象都应该有这个标记
public class TestDeadLock implements Runnable {
static Object o1 = new Object(), o2 = new Object();
boolean flag = true;
public static void main(String[] args) {
TestDeadLock tdl1 = new TestDeadLock();
TestDeadLock tdl2 = new TestDeadLock();
tdl2.flag = false;
Thread t1 = new Thread(tdl1);
Thread t2 = new Thread(tdl2);
t1.start();
t2.start();
}
public void run() {
System.out.println(flag);
if(flag) {
synchronized(o1) {
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
ie.printStackTrace();
}
synchronized(o2) {
System.out.println("o2");
}
}
}
else {
synchronized(o2) {
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
ie.printStackTrace();
}
synchronized(o1) {
System.out.println("o1");
}
}
}
}
}
哲学家吃饭问题:
class Philosopher implements Runnable{
int id;
Chopstick chopstick;
boolean flag = true;
Philosopher(int id, Chopstick chopstick) {
this.id = id;
this.chopstick = chopstick;
}
void eat(){
while(!chopstick.getChopstick(id)) {
}
System.out.println("Philosopher"+id+" is eating");
try {
Thread.sleep(1000);
} catch(InterruptedException ie) {
ie.printStackTrace();
}
chopstick.releaseChopstick(id);
}
void think() {
System.out.println("Philosopher"+id+" is thinking");
try {
Thread.sleep(5000);
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
public void run() {
int i = 0;
System.out.println("Philosopher"+id+" starts");
while(flag) {
eat();
think();
i++;
if(i>5)
shutdown();
}
}
public void shutdown() {
flag = false;
}
}
class Chopstick {
boolean[] chopstick;
Chopstick() {
chopstick = new boolean[6];
for(int j = 0; j < 6; j++)
chopstick[j] = false;
}
synchronized boolean getChopstick(int id) {
int right = (id+1) % chopstick.length, left = id;
if((!chopstick[left]) && (!chopstick[right])) {
chopstick[left] = true;
chopstick[right] = true;
System.out.println("Philosopher"+id+" gets chopstick"+left+" and chopstick"+right);
return true;
}
return false;
}
synchronized void releaseChopstick(int id) {
int right = (id+1) % chopstick.length, left = id;
chopstick[left] = false;
chopstick[right] = false;
}
}
public class PhilosopherEat {
public static void main(String[] args) {
Chopstick chopstick = new Chopstick();
for(int i = 0; i < 6; i++) {
new Thread(new Philosopher(i, chopstick)).start();
}
}
}
面试题:
如以下程序,运行m2方法是哪一个线程?main函数
public class TT implements Runnable {
int b = 100;
public synchronized void m1() throws Exception{
//Thread.sleep(2000);
b = 1000;
Thread.sleep(5000);
System.out.println("b = " + b);
}
public void m2() throws Exception {
Thread.sleep(2500);
b = 2000;
}
public void run() {
try {
m1();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
TT tt = new TT();
Thread t = new Thread(tt);
t.start();
tt.m2();
System.out.println(tt.b);
}
}结果:
2000
b = 2000
多生产者多消费者:this.wait(),阻塞当前访问对象的线程(一个对象必须获取锁之后才能使用wait方法,因为没锁住,怎么能阻塞,阻塞之后,锁就不再归我所有,只有被叫醒之后才重新有锁,这个与sleep方法不同,因为sleep阻塞之后,不释放锁)
this.notify(),叫醒一个正在等待我这个对象的线程
this.notifyAll(),不叫醒自己
class Producer implements Runnable{
int id;
Stack stack = null;
int pid = 1;
Producer(int id, Stack stack) {
this.id = id;
this.stack = stack;
}
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("Producer"+id+" Product:"+id+":"+pid);
stack.push(id+":"+pid);
pid ++;
try {
Thread.sleep((long)(Math.random()*500));
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
int id;
Stack stack = null;
Consumer(int id, Stack stack) {
this.id = id;
this.stack = stack;
}
public void run(){
for(int i = 0; i < 5; i++) {
Product p = stack.pop();
System.out.println("Consumer"+id+" "+p);
try {
Thread.sleep((long)(Math.random()*1000));
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
class Stack {
Product[] products = new Product[6];
int index = 0;
synchronized void push(String id) {
while(index == 6) { //采用if的哈,如果发生异常跳出if之后,继续处理下面部分,所以采用while比较好
try {
this.wait(); //释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Product p = new Product(id);
products[index] = p;
index++;
this.notifyAll(); //通知等待这个对象的线程,就是检测到index==0那个线程
}
synchronized Product pop() {
while(index == 0) {//采用if的哈,如果发生异常跳出if之后,继续处理下面部分,所以采用while比较好
try {
this.wait(); //释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
this.notifyAll(); //通知等待这个对象的线程,就是发现index == 6那个线程
return products[index];
}
}
class Product {
String id;
Product(String id) {
this.id = id;
}
public String toString() {
return "Product:"+id;
}
}
public class ProducerConsumer {
public static void main(String[] args) {
Stack stack = new Stack();
Thread t = new Thread(new Producer(1, stack));
Thread t1 = new Thread(new Producer(2, stack));
Thread t2 = new Thread(new Consumer(1, stack));
Thread t3 = new Thread(new Consumer(2, stack));
t.start();
t1.start();
t2.start();
t3.start();
}
}
JAVA还提供了很多其他的API来来实现线程的暂停,恢复,结束等,但是几乎都被废止了,因为会引入一些缺点,比如stop方法会引入一个ThreadDeath异常,之前所有的获得锁都会被释放,会造成inconsistency
Reference:
1. http://blog.youkuaiyun.com/hudashi/article/details/6958550
2. 马士兵JAVA基础教程
3. http://m.blog.youkuaiyun.com/blog/u010802573/38661719
本文深入探讨Java中线程的概念,从进程与线程的区别出发,详细介绍了如何通过Runnable接口和Thread类实现线程运行体。进一步阐述了线程同步机制,包括synchronized关键字的使用以及死锁的预防。并通过实例展示了线程之间的协作与冲突解决策略。
10万+

被折叠的 条评论
为什么被折叠?



