线程
1.创建线程
Java使用下列类/接口实现线程功能:
Runnable接口
Thread类
ThreadGroup类
例如使用Runnable接口实现线程功能:
创建的线程类
Class WorkThread implements Runnable{
Public void run(){
....................
}
}
在创建这个线程类之后,需要创建Thread类的实例,然后将WorkThread类的对象作为参数传递给Thread类的对象作为参数传递给Thread类的构造函数,如下:
Thread t = new Thread(new WorkThread());
创建的线程会执行WorkThread类定义的run方法。当run方法执行完之后,此线程就死亡了,并且不能被重新安排另一次运行。
另外可以通过子类化Thread类来创建线程:
Class WorkerThread extends Thread{
Public void run(){
................
}
}
通过重写run方法来实现自己期望的功能。
Thread t = new WorkerThread();
T.start();
在线程启动时,并不意味着线程会立即获得CPU。相反,线程会被放入准备运行队列中,之前讨论过,线程最终会获得CPU时间片来执行。
注意
实现Runnable接口被认为是一种面向对象的方法,这种方法被推荐使用,优于通过子类化Thread类的方法。而且,如果你的类已经扩展了其他类,就不允许同时扩展Thread类。
ThreadGroup类允许将所有逻辑上相关的线程归到组中,这样你就能同时改变属于某个组的所有线程。默认情况下:创建的所有线程属于同一个组。然而,也可以创建自己的其他组,然后向其中添加新创建的线程。线程组可能包含其他的线程组,因此,可以为线程构建树型层次结构。
1.新建状态
线程在已经利用new关键字创建但是还未执行的这段时间里,处于一种特殊的新建状态中,此时,线程对象已经被分配了内存空间,私有数据已经被初始化,但是该线程尚未被调度。此时的线程可以被调度,变成可运行状态,也可以被杀死,变成死亡状态。
2.就绪状态
在处于创建状态的线程中调用start()方法将线程的状态转换为就绪状态。这是,线程已经得到除CPU时间之外的其他系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会
3.运行状态表明线程正在运行,该线程已经拥有了对CPU的控制权。这个线程一直运行到运行完毕,除非该线程主动放弃CPU的控制权或者CPU的控制权被优先级更高的线程抢占,处在运行状态的线程在下列情况下将让出CPU的控制权:
(1)线程运行完毕;
(2)有比当前线程优先级更高的线程进入可运行状态;
(3)线程主动睡眠一段时间;
(4)线程在等待某一资源。
4.如果一个线程处于挂起状态,那么这个线程暂时无法进入就绪队列。处于挂起状态的线程通常需要由某些事件才能唤醒,至于由什么事件唤醒该线程,则取决于其挂起的原因。处于睡眠状态的线程必须被挂起一段固定事件,当睡眠事件结束时就可以变成运行状态;因等待资源或消息而被挂起的线程则需要由一个外来事件唤醒。
5.正常情况下run()返回使得线程死亡。调用stop()或destroy()亦有同样的效果,但是不被推荐,因为前者会产生异常,后者是强制终止,不会释放内存。
方法 | 说明 | 有效状态 | 目标状态 |
Stat() | 开始执行线程 | 新建 | 运行 |
Stop() | 终止执行线程 | 运行 | 死亡 |
Sleep() | 睡眠指定毫秒长的时间 | 运行 | 就绪 |
Sleep() | 睡眠指定长的时间,允许纳秒级控制 | 运行 | 就绪 |
Suspend() | 挂起线程 | 运行 | 就绪 |
Resume() | 继续执行 | 就绪 | 运行 |
Yield() | 暂时释放线程 | 运行 | 运行 |
Join() | 调用该方法的线程转为挂起状态,直到本线程进入死亡状态才再次进入可运行状态 | 运行 | 挂起 |
线程同步是指java避免多个线程同时访问一个数据而造成数据混乱的方法。它可以避免多个线程同时访问相同的数据时,产生线程之间的争抢,避免一个线程刚生成的数据又会被其他线程生成的数据所覆盖。
Java用监听器的手段来完成线程的同步,就好像监听器把受保护的资源外面添加了一把锁,而这把锁只有一把钥匙。每个线程只有在得到这把钥匙之后才可以对被保护的资源执行操作,而其他的线程只能等待,直到能拿到这把钥匙。
方法同步:
一个类中任何方法都可以设计成为synchronized方法,以防止多线程数据崩溃。当一个线程进入synchronized方法后,能保证在其他任何线程访问这个方法之前完成自己的一次执行。如果一个线程试图访问一个已经启动的synchronized方法,则这个线程必须等待,直到已启动线程执行完毕,释放这个synchronized方法后才能访问。
例子:
public class DepositThread implements Runnable{
Account acc;
private static int NUM_OF_THREAD = 100;
static Thread[] threads = new Thread[NUM_OF_THREAD];
public DepositThread(Account acc){
this.acc = acc;
}
@Override
public void run() {
// TODO Auto-generated method stub
acc.deposit(20.0f);
acc.withdraw(10.0f);
}
public static void main(String[] args) {
final Account acc = new Account("王红", 1000.0f);
for(int i = 0; i < NUM_OF_THREAD; i++){
DepositThread my = new DepositThread(acc);
threads[i] = new Thread(my);
threads[i].start();
}
for(int i = 0; i < NUM_OF_THREAD; i++){
try {
threads[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("完成,王红的账户余额为:" + acc.getBalance());
}
}
class Account{
String name;
float amount;
public Account(String name, float amount){
this.name = name;
this.amount = amount;
}
public synchronized void deposit(float amt){
float tmp = amount;
tmp += amt;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
amount = tmp;
}
public synchronized void withdraw(float amt){
float tmp = amount;
tmp -= amt;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
amount = tmp;
}
public float getBalance(){
return amount;
}
}
方法同步和对象同步有同样的效果。
例如:
Public synchronized void yourMethod(){
...................
}
与下面代码效果相同
Public void yourMethod(){
Synchronized(this){
........
}
}
区别:有时,一个方法执行时间很长,而其中只有很短的一段时间访问关键数据,在这种情况下,将整个方法声明为synchronized,将导致其他线程因无法调用该线程的synchronized方法进行操作而长时间无法继续执行,这在整体效率上是不划算的。此时,就可以使用对象同步,只把访问关键数据的代码段用花括号括起来,在其前加上synchronized(this)即可。
线程组的各种方法:
public class TheadGroupTest {
public void test(){
ThreadGroup tg = new ThreadGroup("test");
Thread A = new Thread(tg,"A");
Thread B = new Thread(tg, "B");
Thread C = new Thread(tg, "C");
A.setPriority(6); //设置线程A的优先级为6
C.setPriority(4); //设置线程C的优先级为4,线程B的默认为5
System.out.println("tg线程组正在活动的线程个数:" + tg.activeCount());
System.out.println("线程A的优先级是:" + A.getPriority());
//设置线程组tg的优先级,但是不能改变A,B,C各自的优先级
tg.setMaxPriority(8);
System.out.println("tg线程组的优先级是:" + tg.getMaxPriority());
System.out.println("tg线程组上一级线程组信息:" + tg.getParent());
System.out.println("线程组名称:" + tg.getName());
System.out.println("tg线程组的信息:");
tg.list(); //对每个线程组中的线程调用toString()方法,返回线程组的名称和优先级
}
public static void main(String[] args) {
TheadGroupTest ttg = new TheadGroupTest();
ttg.test();
}
}
线程之间的通信:
多线程的一个重要特点是它们之间可以互相通信,线程通信使线程之间可以相互交流和等待,可以通过经常共享的数据使线程相互交流,也可以通过线程控制方法使线程相互等待。Object类为此提供了3个方法:wait(),notify(),notifyAll()。
Wait()使当前线程处于等待状态。
Notify()唤醒等待该对象线程
NotifyAll()唤醒所有等待该对象的所有线程
典型的线程间通信建立在生产者和消费者模型上:一个线程产生输出(相当于生产产品),另一个线程使用输入(相当于消费产品),先有生产者生产,才能有消费者消费。生产者未生产之前,通知消费者等待;生产后通知消费者消费,消费者消费以后再通知生产者生产,这就是等待通知机制。
代码如下:
public class ProducerThread {
public static void main(String[] args) {
Store s = new Store();
Producer pd = new Producer(s);
Consumer cs = new Consumer(s);
new Thread(pd).start();
new Thread(cs).start();
}
}
//仓库
class Store{
int product = 0;
boolean bfull = false; //仓库中是否有产品的判断条件,初始条件是没有产品
public synchronized void put(int product){
if(bfull){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.product = product;
System.out.println("生产者生产的产品:" + product);
bfull = true;
notify();
}
public synchronized void get(){
if(!bfull){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("消费者消费的产品:" + product);
bfull = false;
notify();
}
}
class Producer implements Runnable{
Store s;
public Producer(Store s){
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 1; i <= 10; i++){
s.put(i);
}
}
}
class Consumer implements Runnable{
Store s;
public Consumer(Store s){
this.s = s;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 1; i <= 10; i++){
s.get();
}
}
}