线程的睡眠是可以被打断的,通过Threadinterrupt0),当然一个线程中可以调用另外一个线程的 interrupt0),线程的睡眠被打断后进入 Runnable 状态。 由于 Thread.sleep(的定义中通过 throw 关键字声明该方法中有可能引发异常,所以,我们的程序在调用该方法时,必须使用 try...catch 代码块处理,否则,编译将出错,这正是 Java 语言强健性的一个方面
public class ThreadDemo5{
public static void main(String [] args){
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class ThreadTest implements Runnable{
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
try{
Thread.sleep(10);
}catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName()+"is saling ticket" + tickets--);
}
}
}
}
public class ThreadDemo8{
public static void main(String [] args){
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class ThreadTest implements Runnable{
private int tickets = 100;
String str = new String(" ");
public void run(){
while(true){
synchronized(str){
if(tickets > 0){
try{
Thread.sleep(10);
}catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName()+"is saling ticket" + tickets--);
}
}
}
}
}
任意类型的对象都有一个标志位,该标志位具有0、1 两种状态,其开始状态为 1,当执行 synchronized(object)语句后,object 对象的标志位变为 0状态,直到执行完整个synchronized语句中的代码块后又回到1状态。一个线程执行到synchronized(obiect)语句处时,先检查 obiect 对象的标志位,如果为0状态,表明已经有另外的线程的执行状态正在有关的同步代码块中,这个线程将暂时阻塞,让出 CPU 资源,直到另外的线程执行完有关的同步代码块,将 obiect 对象的标志位恢复到1状态,这个阻塞就被取消,线程能够继续往下执行,并将 obiect 对象的标志位变为0状态,防止其他线程再进入有关的同步代码块中。如果有多个线程因等待同一对象的标志位而处于阻寒状态时,当对象的标志位恢复到1状态时,只会有一个线程能够继续运行,其他线程仍然处于阻塞等待状态。我们反复提到有关的同步代码块,是指不仅同一个代码块在多个线程间可以实现同步(像上面例子一样),若干个不同的代码块也可以实现相互之间的同步,只要各 synchronized(object)语句中的 object 完全是同一个对象就可以。上面的讲解主要是为了达到通俗易懂的目的,但有时与同行交流,或是参看相关书籍时,我们也不得不掌握一些专业术语,下面是对刚才的内容用专业术语进行的陈述
当线程执行到synchronized 的时候,检查传入的实参对象,并得到该对象的锁旗标(即是我们上面讲的标志位)。如果得不到,那么此线程就会被加入到一个与该对象的锁旗标相关连的等待线程池中,一直等到该对象的锁旗标被归还,池中的等待线程就会得到该旗标,然后继续执行下去。当线程执行完成同步代码块时,就会自动释放它占有的同步对象的锁旗标。一个用于 synchronized 语句中的对象称为一个监视器,当一个线程获得了synchronized(object)语句中的代码块的执行权,即意味着它锁定了监视器,在一段时间内只能有一个线程可以锁定监视器。所有其他的线程在试图进入已锁定的监视器时将被挂起直到锁定了监视器的线程执行完 synchronized(obiect)语句中的代码块,即监视器被解锁为止,另外的线程才可以进入并锁定监视器,一个刚锁定了监视器的线程在监视器被解锁后可以再次进入并锁定同一监视器,好比篮球运动员的篮球出手后可以再次去抢回来一样。另外,当在同步块中遇到 break 语句或抛出异常时,线程也会释放该锁旗标
其实,程序并不能控制CPU 的切换,程序是不可能抱着 CPU 的大腿不让他走的。当CPU 进入了一段同步代码块中执行,CPU 是可以切换到其他线程的,只是在准备执行其他线程的代码时,发现其他线程处于阻塞状态,CPU 又会回到先前的线程上。这个过程就类似于幸运之神刚一光顾其他有关线程,没想到吃了个闭门羹,便又离开了
public class ThreadDemo9{
public static void main(String [] args){
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class ThreadTest implements Runnable{
private int tickets = 100;
String str = new String(" ");
public void run(){
while(true){
sale();
}
}
public synchronized void sale(){
if(tickets > 0){
try{
Thread.sleep(10);
}catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName()+"is saling ticket" + tickets--);
}
}
}
在同一类中,使用 synchronized 关键字定义的若干方法,可以在多个线程之间同步,当有一个线程进入了 synchronized 修饰的方法(获得监视器),其他线程就不能进入同个对象的所有使用了 synchronized 修饰的方法,直到第一个线程执行完它所进入的synchronized 修饰的方法为止(离开监视器)
public class ThreadDemo01{
public static void main(String [] args){
ThreadTest t = new ThreadTest();
new Thread(t).start();
t.str = new String("method");
new Thread(t).start();
}
}
class ThreadTest implements Runnable{
private int tickets=100;
String str = new String(" ");
public void run(){
if(str.equals("method")){
while(true){
sale();
}
}else{
synchronized(str){
if(tickets>0){
try{
Thread.sleep(10);
}catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName()+"is saling ticket" + tickets--);
}
}
}
}
public synchronized void sale(){
if(tickets > 0){
try{
Thread.sleep(10);
}catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println(Thread.currentThread().getName()+"is saling ticket" + tickets--);
}
}
}
如果一个线程刚执行完 push 方法中的 data[idx]=c 语,CPU 便切换到了另外一个线程上执行 push 方法,第二个线程将覆盖掉第一个线程执行的 datalidx]=c 语句的结果
另外,共享访问的数据,应当是类的 private 数据成员,从而禁止来自类外的随意访问破坏数据的一致性
在实际项目中,多线程安全问题会有许多。多线程访问共享数据要十分小心,线程同步的错误十分隐蔽,总是不能马上发现,且极难排查。对一个大的程序,如果不事先仔细考虑,等到程序写完了,在运行过程中发现程序的不稳定性后,再去查找,就更是困难和费时了。只要程序没有多线程安全问题,就不应该使用同步技术,因为源程序调用了同步方法,需要额外的监视器检查,运行效率要低些。
死锁是一种少见的、而且难于调试的错误,在两个线程对两个同步对象具有循环依赖时,就会出现死锁。例如,一个线程进入对象X 的监视器,而另一个对象进入了对象丫的监视器,这时进入X 对象监视器的线程如果还试图进入Y对象的监视器就会被阻隔,接着进入丫对象监视器的线程如果试图进入X对象的监视器也会被阻隔,这样两个线程都处于挂起状态。程序发生死锁后最明显的特征就是程序的运行处于停滞不前状态。这就好比两个人在吃饭,甲拿到了一根筷子和一把刀子,乙拿到了一把叉子和一根筷子,他们都无法吃到饭
class A{
synchronized void foo(B b){
String name = Thread.currentThread().getName();
System.out.println(name+"entered A.foo");
try {
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
System.out.println(name+"trying to call B.last()");
b.last();
}
synchronized void last(){
System.out.println("inside A.last");
}
}
class B{
synchronized void bar(A a){
String name = Thread.currentThread().getName();
System.out.println(name+"entered B.bar");
try {
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
System.out.println(name+"trying to call A.last");
a.last();
}
synchronized void last(){
System.out.println("inside A.last");
}
}
class Deadlock implements Runnable{
A a=new A();
B b=new B();
Deadlock(){
Thread.currentThread().setName("MainThread");
new Thread(this).start();
a.foo(b);
System.out.println("back in main thread");
}
public void run(){
Thread.currentThread().setName("RacingThread");
b.bar(a);
System.out.println("back in other thread");
}
public static void main(String[] args){
new Deadlock();
}
}