1つは、同期コードブロックです。
最初にコードを投稿してください:
public class MyRunnable implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
if(ticket == 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
}
}
}
}
public class Demo {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr1);
Thread t3 = new Thread(mr1);
t1.setName("#窗口一#");
t2.setName("##窗口二##");
t3.setName("###窗口三###");
t1.start();
t2.start();
t3.start();
}
}
ここでのコードは、説明が必要なコンテンツを引き出すために、以前のブログ投稿(チケット販売ケースの実装)とは異なります。
違いは、whileループの判定条件がtrueに変更され、スレッドのスリープが増加することです。
問題:上記のコードの実行中に、重複投票と反対投票が発生します。
その理由は、スリーププロセス中に、スレッドAの制御が他のスレッド(たとえば、B)に与えられるためです。このプロセスでは、スレッドBが共有データチケットを変更するため、スレッドAが印刷するとき、チケットはすでにそうではありません。チケットが変更されたこと、彼は変更しました。
また、チケットがゼロに設定されている場合、判断が下される前にデータ値が変更され(ゼロはループを終了します)、無限ループになる可能性が非常に高くなります。
if判定条件を<= 0に変更すると、無限ループは表示されませんが、重複投票と反対投票が残ります。
したがって、他のスレッドの実行中に共有データの操作が許可されていない場合、この状況は解決されます。
これには、synchronized()メソッドが必要です。共有データを操作する複数のコードをロックして、同期されたコードブロックを実現します。
synchronized(任意对象){
操作共享数据的多条语句
}
デフォルトでは、コードを実行するスレッドがある限り、ロックは開かれ、閉じられます。
スレッドが実行されると、ロックが自動的に開かれます。
同期の長所と短所:
- 利点:マルチスレッドデータセキュリティの問題を解決します。
- 短所:スレッドが多い場合、すべてのスレッドが同期ロックを判断します。これは非常にリソースを消費し、プログラム実行の効率を実質的に低下させます。
2つ目は、ロックオブジェクトが一意であるということです。
上記のチケット販売の場合に使用される実装方法は、Runnableインターフェースを継承することであり、同じパラメーターがスレッド間で使用されるため、ロックも共有されます。
各スレッドに独自のロックがある場合でも、投票の重複と反対票の問題が発生します。つまり、スレッドが安全でなくなります。(Threadクラスを継承してマルチスレッドを実装する場合)
public class MyThread extends Thread {
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized(obj){
if(ticket <= 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
これは、MyThreadクラスのチケット変数とObjectオブジェクトを静的に設定して、すべてのMyThreadクラスオブジェクトが静的変数を共有するようにするためです。
3、同期方法
同期メソッドのロックオブジェクトは次のとおりです。this
同期静的メソッドのロックオブジェクトは次のとおりです。class.class
public class MyRunnable implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean res = synchronizedMethod();
if(res){
break;
}
}
if("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
synchronized(this){
if(ticket == 0){
break;
}else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
}
}
}
}
}
private synchronized boolean synchronizedMethod() {
if (ticket == 0) {
return true;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
return false;
}
}
}
public class Demo {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr1);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
注:このコードがマルチスレッドを実装する方法は、Runnableインターフェースを継承することです。2つのスレッドのパラメーターは同じであり、MyRunnableのオブジェクトmr1のrun()メソッドが実行されるため、これは同じです。
同期静的メソッド:
public class MyRunnable implements Runnable{
//静态方法只能访问静态变量,所以要加上static进行修饰
private static int ticket = 100;
@Override
public void run() {
while (true) {
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean res = synchronizedMethod();
if(res){
break;
}
}
if("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
//这里的对象需要进行修改
synchronized(MyRunnable.class){
if(ticket == 0){
break;
}else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
}
}
}
}
}
private static synchronized boolean synchronizedMethod() {
if (ticket == 0) {
return true;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
return false;
}
}
}
四、ロック
同期コードブロックのロックオブジェクトの問題と同期方法は理解できますが、どこにロックが追加され、どこでロックが解除されるかは直接わかりません。ロックとロック解除の方法をより明確に表現するために、 JDK5の後に提供されます。新しいロックオブジェクトLock
Lockの実装は、同期されたメソッドとステートメントを使用して取得できるよりも幅広いロック操作を提供します。Lockは、ロックを取得および解放するためのメソッドを提供します。
- void lock():ロックを取得します
- voidunlock():ロックを解除します
Lockはインターフェースであり、直接インスタンス化することはできません。ここでは、その実装クラスReentrantLockを使用してインスタンス化します。
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
private static int ticket = 100;
// private static Object obj = new Object();
ReentrantLock rlock = new ReentrantLock();
@Override
public void run() {
while (true) {
// synchronized(obj){
try {
rlock.lock();
if (ticket <= 0) {
break;
} else {
Thread.sleep(100);
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket + "张");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rlock.unlock();
}
// }
}
}
}
5、デッドロック
デッドロックはロックのネストによって発生します。デッドロックを回避するために、ロックのネストを記述しないことをお勧めします。