启动一个程序,是一个进程
一个进程内并行执行的是多个线程
这里以把同一个数循环50次加1为例。结果和预想有出入,请看线程同步部分。
想操作同一个数请把该数设为static!!!
设计一个类,继承Thread,并且重写run方法
启动线程办法:调用其start方法
public class AddThread1 extends Thread {
private static Integer num = 0;
public AddThread1(Integer num) {
this.num = num;
}
public void add() throws InterruptedException {
Thread.sleep(50);
num = num + 1;
System.out.println(currentThread().getName() + ":" + num);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行线程:
AddThread1 addThread1 = new AddThread1(num);
AddThread1 addThread11 = new AddThread1(num);
addThread1.start();
addThread11.start();
implements Runnable
创建类,实现Runnable接口
启动的时候,首先创建一个对象,然后再根据该对象创建一个线程对象,并启动
public class AddThread2 implements Runnable {
private static Integer num;
public AddThread2(Integer num) {
this.num = num;
}
public void add() throws InterruptedException {
Thread.sleep(50);
num = num + 1;
System.out.println(Thread.currentThread().getName()+ ":" + num);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行线程:
AddThread2 addThread2 = new AddThread2(num);
new Thread(addThread2).start();
new Thread(addThread2).start();
匿名类
使用匿名类,继承Thread,重写run方法,直接在run方法中写业务代码
匿名类的一个好处是可以很方便的访问外部的局部变量。
运行线程:
public class ThreadTest {
private static Integer num = 0;
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run() {
add50();
}
};
t1.start();
Thread t2=new Thread(){
@Override
public void run() {
add50();
}
};
t2.start();
}
public static void add50(){
for (int i=0;i<50;i++){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = num + 1;
System.out.println(Thread.currentThread().getName()+ ":" + num);
}
}
}
常见线程方法
sleep:表示当前线程暂停 ,其他线程不受影响
//休眠1000ms Thread.sleep(1000);
join:即表明在主线程中加入该线程,主线程会等待该线程结束完毕, 才会往下运行。
public class TestThread {
public static void main(String[] args) {
Thread t1= new Thread(){
public void run(){
//todo
}
};
t1.start();
//代码执行到这里,一直是main线程在运行
try {
//t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
setPriority:优先级,当线程处于竞争关系的时候,优先级高的线程会有更大的几率获得CPU资源
Thread t2= new Thread(){
public void run(){
//todo
}
};
t2.setPriority(Thread.MAX_PRIORITY);
yield:当前线程,临时暂停,使得其他线程可以有更多的机会占用CPU资源
Thread t2= new Thread(){
public void run(){
//临时暂停,使得t1可以占用CPU资源
Thread.yield();
//todo
}
};
setDaemon:当一个进程里,所有的线程都是守护线程的时候,结束当前进程。
Thread t1= new Thread(){
public void run(){
//todo
}
};
t1.setDaemon(true);
t1.start();
线程安全的解决方案
上面三种创建线程的方式都是把同一个数0用两个线程循环50次加1,但最后结果都达不到100。
是因为其中一个线程加1的时候,另一个线程不是用的第一个线程加1以后的数据再加1,而是当前的数据,这个数据很可能还没加1。
解决办法有:使用synchronized、使用线程安全的类
所有需要修改数据的地方,有要建立在占有someObject的基础上。
而对象 someObject在同一时间,只能被一个线程占有。 间接地,导致同一时间,数据只能被一个线程修改。
这个 someObject就是个锁,要多个线程共用一个锁线程同步才会生效。
- extends Thread 形式的线程同步:
- 实现Runnable接口的由于是同一个AddThread2类的对象生成了两个线程,所以可以用本对象当锁。
synchronized (this) {
}
表示当前对象为同步对象,即是addThread2为同步对象。
- 匿名内部类用个obj就行
java中这种包装类的自动拆箱装箱特性导致的,在拆装箱的过程中实际上是new了一个新对象代替了原num对象,导致锁变了,如果没有装箱拆箱过程就可以用。
可使用线程安全的AtomicInteger。
在集合中,线程安全的有:HashTable,Vector
在包装类中,线程安全的有:StringBuffer,AtomicInteger等
非线程安全的类转换为线程安全的类:
ArrayList线程交互:
wait()的意思是, 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。
需求:设计一个类,里面一个加1和一个减1方法,创建三个线程,对同一个数进行两个减1和一个加1,当数据到0,不能减1,等待数据大于0才能减1。
分析:减1方法用while循环的时候最好不要一直判断数据是否=0,可以wait()临时释放当前的占用,让加1加完再执行线程,而不是不停的循环判断,占用cpu资源。
public class AddAndSubstractThread {
private static int num;
public AddAndSubstractThread (int num) {
this.num = num;
}
//同步锁是this
public synchronized void add() {
num++;
System.out.println(Thread.currentThread().getName() + ":" + num);
//唤醒其他所有等待在this对象上的线程
this.notifyAll();
}
public synchronized void substract() {
//这里不能用if
//当用if时,num=0只会判断一次,在进入判断条件内部就不会再进行判断了,wait之后只要拿到锁就算num还是0也会减1.
while (num == 0) {
//暂时释放对this的占有,并等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + ":" + num);
}
}
public static void main(String[] args) {
AddAndSubstractThread addAndSubstractThread = new AddAndSubstractThread(5);
Thread add = new Thread() {
@Override
public void run() {
while (true) {
addAndSubstractThread.add();
try {
sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread substract1 = new Thread() {
@Override
public void run() {
while (true) {
addAndSubstractThread.substract();
try {
sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread substract2 = new Thread() {
@Override
public void run() {
while (true) {
addAndSubstractThread.substract();
try {
sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
add.start();
substract1.start();
substract2.start();
}
线程池
每一个线程的启动和结束都是比较消耗时间和占用资源的。
如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢。
为了解决这个问题,引入线程池这种设计思想。
线程池类ThreadPoolExecutor在包java.util.concurrent下
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
- Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
- synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。
所谓的原子性操作即不可中断的操作,比如赋值操作。原子性操作本身是线程安全的
可以使用原子类操作。
JDK6 以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger。
先启动server,再启动client
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket s = new Socket("127.0.0.1", 8888);
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("中英文混合alkjds");
dos.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8888);
Socket s = ss.accept();
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
String msg = dis.readUTF();
System.out.println(msg);
dis.close();
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
双向通信
双向通信要用到多线程,这里可以把发送消息和接收消息抽取出来做成单独的两个类。
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
public class ReceiveThread extends Thread {
private Socket s;
public ReceiveThread(Socket s) {
this.s = s;
}
@Override
public void run() {
try (DataInputStream dis = new DataInputStream(s.getInputStream())) {
while (true) {
String msg = dis.readUTF();
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
public class SendThread extends Thread {
private Socket s;
public SendThread(Socket s) {
this.s = s;
}
@Override
public void run() {
try (DataOutputStream dos = new DataOutputStream(s.getOutputStream())) {
Scanner scanner = new Scanner(System.in);
while (true) {
String next = scanner.next();
dos.writeUTF(next);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket s = new Socket("127.0.0.1", 8888);
new SendThread(s).start();
new ReceiveThread(s).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8888);
Socket s = ss.accept();
new SendThread(s).start();
new ReceiveThread(s).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}



